blob: ecb6169c8f8d705c5dbe63d5b05dc740db75d6b6 [file] [log] [blame]
/* 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;
}