| /* Copyright 2016 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <sys/cdefs.h> |
| #include <sys/mman.h> |
| #ifdef __BIONIC__ |
| #include <cutils/ashmem.h> |
| #else |
| #include <sys/shm.h> |
| #endif |
| #include <errno.h> |
| #include <syslog.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "cras_shm.h" |
| |
| int cras_shm_info_init(const char *stream_name, uint32_t length, |
| struct cras_shm_info *info_out) |
| { |
| struct cras_shm_info info; |
| |
| if (!info_out) |
| return -EINVAL; |
| |
| strncpy(info.name, stream_name, sizeof(info.name) - 1); |
| info.name[sizeof(info.name) - 1] = '\0'; |
| info.length = length; |
| info.fd = cras_shm_open_rw(info.name, info.length); |
| if (info.fd < 0) |
| return info.fd; |
| |
| *info_out = info; |
| |
| return 0; |
| } |
| |
| int cras_shm_info_init_with_fd(int fd, size_t length, |
| struct cras_shm_info *info_out) |
| { |
| struct cras_shm_info info; |
| |
| if (!info_out) |
| return -EINVAL; |
| |
| info.name[0] = '\0'; |
| info.length = length; |
| info.fd = dup(fd); |
| if (info.fd < 0) |
| return info.fd; |
| |
| *info_out = info; |
| |
| return 0; |
| } |
| |
| /* Move the resources from the cras_shm_info 'from' into the cras_shm_info 'to'. |
| * The owner of 'to' will be responsible for cleaning up those resources with |
| * cras_shm_info_cleanup. |
| */ |
| static int cras_shm_info_move(struct cras_shm_info *from, |
| struct cras_shm_info *to) |
| { |
| if (!from || !to) |
| return -EINVAL; |
| |
| *to = *from; |
| from->fd = -1; |
| from->name[0] = '\0'; |
| return 0; |
| } |
| |
| void cras_shm_info_cleanup(struct cras_shm_info *info) |
| { |
| if (!info) |
| return; |
| |
| if (info->name[0] != '\0') |
| cras_shm_close_unlink(info->name, info->fd); |
| else |
| close(info->fd); |
| |
| info->fd = -1; |
| info->name[0] = '\0'; |
| } |
| |
| int 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) |
| { |
| struct cras_audio_shm *shm; |
| int ret; |
| |
| if (!header_info || !samples_info || !shm_out) { |
| ret = -EINVAL; |
| goto cleanup_info; |
| } |
| |
| if (samples_prot != PROT_READ && samples_prot != PROT_WRITE) { |
| ret = -EINVAL; |
| syslog(LOG_ERR, |
| "cras_shm: samples must be mapped read or write only"); |
| goto cleanup_info; |
| } |
| |
| shm = calloc(1, sizeof(*shm)); |
| if (!shm) { |
| ret = -ENOMEM; |
| goto cleanup_info; |
| } |
| |
| /* Move the cras_shm_info params into the new cras_audio_shm object. |
| * The parameters are cleared, and the owner of cras_audio_shm is now |
| * responsible for closing the fds and unlinking any associated shm |
| * files using cras_audio_shm_destroy. |
| */ |
| ret = cras_shm_info_move(header_info, &shm->header_info); |
| if (ret) |
| goto free_shm; |
| |
| ret = cras_shm_info_move(samples_info, &shm->samples_info); |
| if (ret) |
| goto free_shm; |
| |
| shm->header = |
| mmap(NULL, shm->header_info.length, PROT_READ | PROT_WRITE, |
| MAP_SHARED, shm->header_info.fd, 0); |
| if (shm->header == (struct cras_audio_shm_header *)-1) { |
| ret = -errno; |
| syslog(LOG_ERR, "cras_shm: mmap failed to map shm for header."); |
| goto free_shm; |
| } |
| |
| shm->samples = mmap(NULL, shm->samples_info.length, samples_prot, |
| MAP_SHARED, shm->samples_info.fd, 0); |
| if (shm->samples == (uint8_t *)-1) { |
| ret = -errno; |
| syslog(LOG_ERR, |
| "cras_shm: mmap failed to map shm for samples."); |
| goto free_shm; |
| } |
| |
| cras_shm_set_volume_scaler(shm, 1.0); |
| |
| *shm_out = shm; |
| return 0; |
| |
| free_shm: |
| cras_audio_shm_destroy(shm); |
| cleanup_info: |
| cras_shm_info_cleanup(samples_info); |
| cras_shm_info_cleanup(header_info); |
| return ret; |
| } |
| |
| void cras_audio_shm_destroy(struct cras_audio_shm *shm) |
| { |
| if (!shm) |
| return; |
| |
| if (shm->samples != NULL && shm->samples != (uint8_t *)-1) |
| munmap(shm->samples, shm->samples_info.length); |
| cras_shm_info_cleanup(&shm->samples_info); |
| if (shm->header != NULL && |
| shm->header != (struct cras_audio_shm_header *)-1) |
| munmap(shm->header, shm->header_info.length); |
| cras_shm_info_cleanup(&shm->header_info); |
| free(shm); |
| } |
| |
| /* Set the correct SELinux label for SHM fds. */ |
| static void cras_shm_restorecon(int fd) |
| { |
| #ifdef CRAS_SELINUX |
| char fd_proc_path[64]; |
| |
| if (snprintf(fd_proc_path, sizeof(fd_proc_path), "/proc/self/fd/%d", |
| fd) < 0) { |
| syslog(LOG_WARNING, |
| "Couldn't construct proc symlink path of fd: %d", fd); |
| return; |
| } |
| |
| /* Get the actual file-path for this fd. */ |
| char *path = realpath(fd_proc_path, NULL); |
| if (path == NULL) { |
| syslog(LOG_WARNING, "Couldn't run realpath() for %s: %s", |
| fd_proc_path, strerror(errno)); |
| return; |
| } |
| |
| if (cras_selinux_restorecon(path) < 0) { |
| syslog(LOG_WARNING, "Restorecon on %s failed: %s", fd_proc_path, |
| strerror(errno)); |
| } |
| |
| free(path); |
| #endif |
| } |
| |
| #ifdef __BIONIC__ |
| |
| int cras_shm_open_rw(const char *name, size_t size) |
| { |
| int fd; |
| |
| /* Eliminate the / in the shm_name. */ |
| if (name[0] == '/') |
| name++; |
| fd = ashmem_create_region(name, size); |
| if (fd < 0) { |
| fd = -errno; |
| syslog(LOG_ERR, "failed to ashmem_create_region %s: %s\n", name, |
| strerror(-fd)); |
| } |
| return fd; |
| } |
| |
| int cras_shm_reopen_ro(const char *name, int fd) |
| { |
| /* After mmaping the ashmem read/write, change it's protection |
| bits to disallow further write access. */ |
| if (ashmem_set_prot_region(fd, PROT_READ) != 0) { |
| fd = -errno; |
| syslog(LOG_ERR, "failed to ashmem_set_prot_region %s: %s\n", |
| name, strerror(-fd)); |
| } |
| return fd; |
| } |
| |
| void cras_shm_close_unlink(const char *name, int fd) |
| { |
| close(fd); |
| } |
| |
| #else |
| |
| int cras_shm_open_rw(const char *name, size_t size) |
| { |
| int fd; |
| int rc; |
| |
| fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600); |
| if (fd < 0) { |
| fd = -errno; |
| syslog(LOG_ERR, "failed to shm_open %s: %s\n", name, |
| strerror(-fd)); |
| return fd; |
| } |
| rc = posix_fallocate(fd, 0, size); |
| if (rc) { |
| rc = -errno; |
| syslog(LOG_ERR, "failed to set size of shm %s: %s\n", name, |
| strerror(-rc)); |
| return rc; |
| } |
| |
| cras_shm_restorecon(fd); |
| |
| return fd; |
| } |
| |
| int cras_shm_reopen_ro(const char *name, int fd) |
| { |
| /* Open a read-only copy to dup and pass to clients. */ |
| fd = shm_open(name, O_RDONLY, 0); |
| if (fd < 0) { |
| fd = -errno; |
| syslog(LOG_ERR, |
| "Failed to re-open shared memory '%s' read-only: %s", |
| name, strerror(-fd)); |
| } |
| return fd; |
| } |
| |
| void cras_shm_close_unlink(const char *name, int fd) |
| { |
| shm_unlink(name); |
| close(fd); |
| } |
| |
| #endif |
| |
| void *cras_shm_setup(const char *name, size_t mmap_size, int *rw_fd_out, |
| int *ro_fd_out) |
| { |
| int rw_shm_fd = cras_shm_open_rw(name, mmap_size); |
| if (rw_shm_fd < 0) |
| return NULL; |
| |
| /* mmap shm. */ |
| void *exp_state = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, |
| MAP_SHARED, rw_shm_fd, 0); |
| if (exp_state == (void *)-1) |
| return NULL; |
| |
| /* Open a read-only copy to dup and pass to clients. */ |
| int ro_shm_fd = cras_shm_reopen_ro(name, rw_shm_fd); |
| if (ro_shm_fd < 0) |
| return NULL; |
| |
| *rw_fd_out = rw_shm_fd; |
| *ro_fd_out = ro_shm_fd; |
| |
| return exp_state; |
| } |