Permalink
Cannot retrieve contributors at this time
| /* | |
| * osspd - OSS Proxy Daemon: emulate OSS device using CUSE | |
| * | |
| * Copyright (C) 2008-2010 SUSE Linux Products GmbH | |
| * Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org> | |
| * | |
| * This file is released under the GPLv2. | |
| */ | |
| #define FUSE_USE_VERSION 28 | |
| #define _GNU_SOURCE | |
| #include <assert.h> | |
| #include <cuse_lowlevel.h> | |
| #include <fcntl.h> | |
| #include <fuse_opt.h> | |
| #include <libgen.h> | |
| #include <limits.h> | |
| #include <pthread.h> | |
| #include <pwd.h> | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <signal.h> | |
| #include <sys/mman.h> | |
| #include <sys/stat.h> | |
| #include <sys/epoll.h> | |
| #include <sys/socket.h> | |
| #include <sys/soundcard.h> | |
| #include <sys/time.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| #include "ossp.h" | |
| #include "ossp-util.h" | |
| #define OSSP_MMAP | |
| #define DFL_MIXER_NAME "mixer" | |
| #define DFL_DSP_NAME "dsp" | |
| #define DFL_ADSP_NAME "adsp" | |
| #define STRFMT "S[%u/%d]" | |
| #define STRID(os) os->id, os->pid | |
| #define dbg1_os(os, fmt, args...) dbg1(STRFMT" "fmt, STRID(os) , ##args) | |
| #define dbg0_os(os, fmt, args...) dbg0(STRFMT" "fmt, STRID(os) , ##args) | |
| #define warn_os(os, fmt, args...) warn(STRFMT" "fmt, STRID(os) , ##args) | |
| #define err_os(os, fmt, args...) err(STRFMT" "fmt, STRID(os) , ##args) | |
| #define warn_ose(os, err, fmt, args...) \ | |
| warn_e(err, STRFMT" "fmt, STRID(os) , ##args) | |
| #define err_ose(os, err, fmt, args...) \ | |
| err_e(err, STRFMT" "fmt, STRID(os) , ##args) | |
| enum { | |
| SNDRV_OSS_VERSION = ((3<<16)|(8<<8)|(1<<4)|(0)), /* 3.8.1a */ | |
| DFL_MIXER_MAJOR = 14, | |
| DFL_MIXER_MINOR = 0, | |
| DFL_DSP_MAJOR = 14, | |
| DFL_DSP_MINOR = 3, | |
| DFL_ADSP_MAJOR = 14, | |
| DFL_ADSP_MINOR = 12, | |
| DFL_MAX_STREAMS = 128, | |
| MIXER_PUT_DELAY = 600, /* 10 mins */ | |
| /* DSPS_MMAP_SIZE / 2 must be multiple of SHMLBA */ | |
| DSPS_MMAP_SIZE = 2 * (512 << 10), /* 512k for each dir */ | |
| }; | |
| struct ossp_uid_cnt { | |
| struct list_head link; | |
| uid_t uid; | |
| unsigned nr_os; | |
| }; | |
| struct ossp_mixer { | |
| pid_t pgrp; | |
| struct list_head link; | |
| struct list_head delayed_put_link; | |
| unsigned refcnt; | |
| /* the following two fields are protected by mixer_mutex */ | |
| int vol[2][2]; | |
| int modify_counter; | |
| time_t put_expires; | |
| }; | |
| struct ossp_mixer_cmd { | |
| struct ossp_mixer *mixer; | |
| struct ossp_mixer_arg set; | |
| int out_dir; | |
| int rvol; | |
| }; | |
| #define for_each_vol(i, j) \ | |
| for (i = 0, j = 0; i < 2; j += i << 1, j++, i = j >> 1, j &= 1) | |
| struct ossp_stream { | |
| unsigned id; /* stream ID */ | |
| struct list_head link; | |
| struct list_head pgrp_link; | |
| struct list_head notify_link; | |
| unsigned refcnt; | |
| pthread_mutex_t cmd_mutex; | |
| pthread_mutex_t mmap_mutex; | |
| struct fuse_pollhandle *ph; | |
| /* stream owner info */ | |
| pid_t pid; | |
| pid_t pgrp; | |
| uid_t uid; | |
| gid_t gid; | |
| /* slave info */ | |
| pid_t slave_pid; | |
| int cmd_fd; | |
| int notify_tx; | |
| int notify_rx; | |
| /* the following dead flag is set asynchronously, keep it separate. */ | |
| int dead; | |
| /* stream mixer state, protected by mixer_mutex */ | |
| int mixer_pending; | |
| int vol[2][2]; | |
| int vol_set[2][2]; | |
| int mmap_fd; | |
| size_t mmap_size; | |
| void *mmap; | |
| void *mmap_addr[2]; | |
| struct ossp_transfer *mmap_transfer; | |
| struct ossp_uid_cnt *ucnt; | |
| struct fuse_session *se; /* associated fuse session */ | |
| struct ossp_mixer *mixer; | |
| }; | |
| struct ossp_dsp_stream { | |
| struct ossp_stream os; | |
| unsigned rw; | |
| unsigned mmapped; | |
| int nonblock; | |
| }; | |
| #define os_to_dsps(_os) container_of(_os, struct ossp_dsp_stream, os) | |
| static unsigned max_streams; | |
| static unsigned umax_streams; | |
| static unsigned hashtbl_size; | |
| static char dsp_slave_path[PATH_MAX]; | |
| static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; | |
| static pthread_mutex_t mixer_mutex = PTHREAD_MUTEX_INITIALIZER; | |
| static unsigned long *os_id_bitmap; | |
| static unsigned nr_mixers; | |
| static struct list_head *mixer_tbl; /* indexed by PGRP */ | |
| static struct list_head *os_tbl; /* indexed by ID */ | |
| static struct list_head *os_pgrp_tbl; /* indexed by PGRP */ | |
| static struct list_head *os_notify_tbl; /* indexed by notify fd */ | |
| static LIST_HEAD(uid_cnt_list); | |
| static int notify_epfd; /* epoll used to monitor notify fds */ | |
| static pthread_t notify_poller_thread; | |
| static pthread_t slave_reaper_thread; | |
| static pthread_t mixer_delayed_put_thread; | |
| static pthread_t cuse_mixer_thread; | |
| static pthread_t cuse_adsp_thread; | |
| static pthread_cond_t notify_poller_kill_wait = PTHREAD_COND_INITIALIZER; | |
| static pthread_cond_t slave_reaper_wait = PTHREAD_COND_INITIALIZER; | |
| static LIST_HEAD(slave_corpse_list); | |
| static LIST_HEAD(mixer_delayed_put_head); /* delayed reference */ | |
| static pthread_cond_t mixer_delayed_put_cond = PTHREAD_COND_INITIALIZER; | |
| static int init_wait_fd = -1; | |
| static int exit_on_idle; | |
| static struct fuse_session *mixer_se; | |
| static struct fuse_session *dsp_se; | |
| static struct fuse_session *adsp_se; | |
| static void put_os(struct ossp_stream *os); | |
| /*************************************************************************** | |
| * Accessors | |
| */ | |
| static struct list_head *mixer_tbl_head(pid_t pid) | |
| { | |
| return &mixer_tbl[pid % hashtbl_size]; | |
| } | |
| static struct list_head *os_tbl_head(uint64_t id) | |
| { | |
| return &os_tbl[id % hashtbl_size]; | |
| } | |
| static struct list_head *os_pgrp_tbl_head(pid_t pgrp) | |
| { | |
| return &os_pgrp_tbl[pgrp % hashtbl_size]; | |
| } | |
| static struct list_head *os_notify_tbl_head(int notify_rx) | |
| { | |
| return &os_notify_tbl[notify_rx % hashtbl_size]; | |
| } | |
| static struct ossp_mixer *find_mixer_locked(pid_t pgrp) | |
| { | |
| struct ossp_mixer *mixer; | |
| list_for_each_entry(mixer, mixer_tbl_head(pgrp), link) | |
| if (mixer->pgrp == pgrp) | |
| return mixer; | |
| return NULL; | |
| } | |
| static struct ossp_mixer *find_mixer(pid_t pgrp) | |
| { | |
| struct ossp_mixer *mixer; | |
| pthread_mutex_lock(&mutex); | |
| mixer = find_mixer_locked(pgrp); | |
| pthread_mutex_unlock(&mutex); | |
| return mixer; | |
| } | |
| static struct ossp_stream *find_os(unsigned id) | |
| { | |
| struct ossp_stream *os, *found = NULL; | |
| pthread_mutex_lock(&mutex); | |
| list_for_each_entry(os, os_tbl_head(id), link) | |
| if (os->id == id) { | |
| found = os; | |
| break; | |
| } | |
| pthread_mutex_unlock(&mutex); | |
| return found; | |
| } | |
| static struct ossp_stream *find_os_by_notify_rx(int notify_rx) | |
| { | |
| struct ossp_stream *os, *found = NULL; | |
| pthread_mutex_lock(&mutex); | |
| list_for_each_entry(os, os_notify_tbl_head(notify_rx), notify_link) | |
| if (os->notify_rx == notify_rx) { | |
| found = os; | |
| break; | |
| } | |
| pthread_mutex_unlock(&mutex); | |
| return found; | |
| } | |
| /*************************************************************************** | |
| * Command and ioctl helpers | |
| */ | |
| static ssize_t exec_cmd_intern(struct ossp_stream *os, enum ossp_opcode opcode, | |
| const void *carg, size_t carg_size, const void *din, size_t din_size, | |
| void *rarg, size_t rarg_size, void *dout, size_t *dout_sizep, int fd) | |
| { | |
| size_t dout_size = dout_sizep ? *dout_sizep : 0; | |
| struct ossp_cmd cmd = { .magic = OSSP_CMD_MAGIC, .opcode = opcode, | |
| .din_size = din_size, | |
| .dout_size = dout_size }; | |
| struct iovec iov = { &cmd, sizeof(cmd) }; | |
| struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 }; | |
| struct ossp_reply reply = { }; | |
| char cmsg_buf[CMSG_SPACE(sizeof(fd))]; | |
| char reason[512]; | |
| int rc; | |
| if (os->dead) | |
| return -EIO; | |
| dbg1_os(os, "%s carg=%zu din=%zu rarg=%zu dout=%zu", | |
| ossp_cmd_str[opcode], carg_size, din_size, rarg_size, | |
| dout_size); | |
| if (fd >= 0) { | |
| struct cmsghdr *cmsg; | |
| msg.msg_control = cmsg_buf; | |
| msg.msg_controllen = sizeof(cmsg_buf); | |
| cmsg = CMSG_FIRSTHDR(&msg); | |
| cmsg->cmsg_level = SOL_SOCKET; | |
| cmsg->cmsg_type = SCM_RIGHTS; | |
| cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); | |
| *(int *)CMSG_DATA(cmsg) = fd; | |
| msg.msg_controllen = cmsg->cmsg_len; | |
| } | |
| if (sendmsg(os->cmd_fd, &msg, 0) <= 0) { | |
| rc = -errno; | |
| snprintf(reason, sizeof(reason), "command sendmsg failed: %s", | |
| strerror(-rc)); | |
| goto fail; | |
| } | |
| if ((rc = write_fill(os->cmd_fd, carg, carg_size)) < 0 || | |
| (rc = write_fill(os->cmd_fd, din, din_size)) < 0) { | |
| snprintf(reason, sizeof(reason), | |
| "can't tranfer command argument and/or data: %s", | |
| strerror(-rc)); | |
| goto fail; | |
| } | |
| if ((rc = read_fill(os->cmd_fd, &reply, sizeof(reply))) < 0) { | |
| snprintf(reason, sizeof(reason), "can't read reply: %s", | |
| strerror(-rc)); | |
| goto fail; | |
| } | |
| if (reply.magic != OSSP_REPLY_MAGIC) { | |
| snprintf(reason, sizeof(reason), | |
| "reply magic mismatch %x != %x", | |
| reply.magic, OSSP_REPLY_MAGIC); | |
| rc = -EINVAL; | |
| goto fail; | |
| } | |
| if (reply.result < 0) | |
| goto out_unlock; | |
| if (reply.dout_size > dout_size) { | |
| snprintf(reason, sizeof(reason), | |
| "data out size overflow %zu > %zu", | |
| reply.dout_size, dout_size); | |
| rc = -EINVAL; | |
| goto fail; | |
| } | |
| dout_size = reply.dout_size; | |
| if (dout_sizep) | |
| *dout_sizep = dout_size; | |
| if ((rc = read_fill(os->cmd_fd, rarg, rarg_size)) < 0 || | |
| (rc = read_fill(os->cmd_fd, dout, dout_size)) < 0) { | |
| snprintf(reason, sizeof(reason), "can't read data out: %s", | |
| strerror(-rc)); | |
| goto fail; | |
| } | |
| out_unlock: | |
| dbg1_os(os, " completed, result=%d dout=%zu", | |
| reply.result, dout_size); | |
| return reply.result; | |
| fail: | |
| warn_os(os, "communication with slave failed (%s)", reason); | |
| os->dead = 1; | |
| return rc; | |
| } | |
| static ssize_t exec_cmd(struct ossp_stream *os, enum ossp_opcode opcode, | |
| const void *carg, size_t carg_size, const void *din, size_t din_size, | |
| void *rarg, size_t rarg_size, void *dout, size_t *dout_sizep, int fd) | |
| { | |
| int is_mixer; | |
| int i, j; | |
| ssize_t ret, mret; | |
| /* mixer command is handled exlicitly below */ | |
| is_mixer = opcode == OSSP_MIXER; | |
| if (is_mixer) { | |
| ret = -pthread_mutex_trylock(&os->cmd_mutex); | |
| if (ret) | |
| return ret; | |
| } else { | |
| pthread_mutex_lock(&os->cmd_mutex); | |
| ret = exec_cmd_intern(os, opcode, carg, carg_size, | |
| din, din_size, rarg, rarg_size, | |
| dout, dout_sizep, fd); | |
| } | |
| /* lazy mixer handling */ | |
| pthread_mutex_lock(&mixer_mutex); | |
| if (os->mixer_pending) { | |
| struct ossp_mixer_arg marg; | |
| repeat_mixer: | |
| /* we have mixer command pending */ | |
| memcpy(marg.vol, os->vol_set, sizeof(os->vol_set)); | |
| memset(os->vol_set, -1, sizeof(os->vol_set)); | |
| pthread_mutex_unlock(&mixer_mutex); | |
| mret = exec_cmd_intern(os, OSSP_MIXER, &marg, sizeof(marg), | |
| NULL, 0, &marg, sizeof(marg), NULL, NULL, | |
| -1); | |
| pthread_mutex_lock(&mixer_mutex); | |
| /* was there mixer set request while executing mixer command? */ | |
| for_each_vol(i, j) | |
| if (os->vol_set[i][j] >= 0) | |
| goto repeat_mixer; | |
| /* update internal mixer state */ | |
| if (mret == 0) { | |
| for_each_vol(i, j) { | |
| if (marg.vol[i][j] >= 0) { | |
| if (os->vol[i][j] != marg.vol[i][j]) | |
| os->mixer->modify_counter++; | |
| os->vol[i][j] = marg.vol[i][j]; | |
| } | |
| } | |
| } | |
| os->mixer_pending = 0; | |
| } | |
| pthread_mutex_unlock(&os->cmd_mutex); | |
| /* | |
| * mixer mutex must be released after cmd_mutex so that | |
| * exec_mixer_cmd() can guarantee that mixer_pending flags | |
| * will be handled immediately or when the currently | |
| * in-progress command completes. | |
| */ | |
| pthread_mutex_unlock(&mixer_mutex); | |
| return is_mixer ? mret : ret; | |
| } | |
| static ssize_t exec_simple_cmd(struct ossp_stream *os, | |
| enum ossp_opcode opcode, void *carg, void *rarg) | |
| { | |
| return exec_cmd(os, opcode, | |
| carg, ossp_arg_sizes[opcode].carg_size, NULL, 0, | |
| rarg, ossp_arg_sizes[opcode].rarg_size, NULL, NULL, -1); | |
| } | |
| static int ioctl_prep_uarg(fuse_req_t req, void *in, size_t in_sz, void *out, | |
| size_t out_sz, void *uarg, const void *in_buf, | |
| size_t in_bufsz, size_t out_bufsz) | |
| { | |
| struct iovec in_iov = { }, out_iov = { }; | |
| int retry = 0; | |
| if (in) { | |
| if (!in_bufsz) { | |
| in_iov.iov_base = uarg; | |
| in_iov.iov_len = in_sz; | |
| retry = 1; | |
| } else { | |
| assert(in_bufsz == in_sz); | |
| memcpy(in, in_buf, in_sz); | |
| } | |
| } | |
| if (out) { | |
| if (!out_bufsz) { | |
| out_iov.iov_base = uarg; | |
| out_iov.iov_len = out_sz; | |
| retry = 1; | |
| } else | |
| assert(out_bufsz == out_sz); | |
| } | |
| if (retry) | |
| fuse_reply_ioctl_retry(req, &in_iov, 1, &out_iov, 1); | |
| return retry; | |
| } | |
| #define PREP_UARG(inp, outp) do { \ | |
| if (ioctl_prep_uarg(req, (inp), sizeof(*(inp)), \ | |
| (outp), sizeof(*(outp)), uarg, \ | |
| in_buf, in_bufsz, out_bufsz)) \ | |
| return; \ | |
| } while (0) | |
| /*************************************************************************** | |
| * Mixer implementation | |
| */ | |
| static void put_mixer_real(struct ossp_mixer *mixer) | |
| { | |
| if (!--mixer->refcnt) { | |
| dbg0("DESTROY mixer(%d)", mixer->pgrp); | |
| list_del_init(&mixer->link); | |
| list_del_init(&mixer->delayed_put_link); | |
| free(mixer); | |
| nr_mixers--; | |
| /* | |
| * If exit_on_idle, mixer for pgrp0 is touched during | |
| * init and each stream has mixer attached. As mixers | |
| * are destroyed after they have been idle for | |
| * MIXER_PUT_DELAY seconds, we can use it for idle | |
| * detection. Note that this might race with | |
| * concurrent open. The race is inherent. | |
| */ | |
| if (exit_on_idle && !nr_mixers) { | |
| info("idle, exiting"); | |
| exit(0); | |
| } | |
| } | |
| } | |
| static struct ossp_mixer *get_mixer(pid_t pgrp) | |
| { | |
| struct ossp_mixer *mixer; | |
| pthread_mutex_lock(&mutex); | |
| /* is there a matching one? */ | |
| mixer = find_mixer_locked(pgrp); | |
| if (mixer) { | |
| if (list_empty(&mixer->delayed_put_link)) | |
| mixer->refcnt++; | |
| else | |
| list_del_init(&mixer->delayed_put_link); | |
| goto out_unlock; | |
| } | |
| /* reap delayed put list if there are too many mixers */ | |
| while (nr_mixers > 2 * max_streams && | |
| !list_empty(&mixer_delayed_put_head)) { | |
| struct ossp_mixer *mixer = | |
| list_first_entry(&mixer_delayed_put_head, | |
| struct ossp_mixer, delayed_put_link); | |
| assert(mixer->refcnt == 1); | |
| put_mixer_real(mixer); | |
| } | |
| /* create a new one */ | |
| mixer = calloc(1, sizeof(*mixer)); | |
| if (!mixer) { | |
| warn("failed to allocate mixer for %d", pgrp); | |
| mixer = NULL; | |
| goto out_unlock; | |
| } | |
| mixer->pgrp = pgrp; | |
| INIT_LIST_HEAD(&mixer->link); | |
| INIT_LIST_HEAD(&mixer->delayed_put_link); | |
| mixer->refcnt = 1; | |
| memset(mixer->vol, -1, sizeof(mixer->vol)); | |
| list_add(&mixer->link, mixer_tbl_head(pgrp)); | |
| nr_mixers++; | |
| dbg0("CREATE mixer(%d)", pgrp); | |
| out_unlock: | |
| pthread_mutex_unlock(&mutex); | |
| return mixer; | |
| } | |
| static void put_mixer(struct ossp_mixer *mixer) | |
| { | |
| pthread_mutex_lock(&mutex); | |
| if (mixer) { | |
| if (mixer->refcnt == 1) { | |
| struct timespec ts; | |
| clock_gettime(CLOCK_REALTIME, &ts); | |
| mixer->put_expires = ts.tv_sec + MIXER_PUT_DELAY; | |
| list_add_tail(&mixer->delayed_put_link, | |
| &mixer_delayed_put_head); | |
| pthread_cond_signal(&mixer_delayed_put_cond); | |
| } else | |
| put_mixer_real(mixer); | |
| } | |
| pthread_mutex_unlock(&mutex); | |
| } | |
| static void *mixer_delayed_put_worker(void *arg) | |
| { | |
| struct ossp_mixer *mixer; | |
| struct timespec ts; | |
| time_t now; | |
| pthread_mutex_lock(&mutex); | |
| again: | |
| clock_gettime(CLOCK_REALTIME, &ts); | |
| now = ts.tv_sec; | |
| mixer = NULL; | |
| while (!list_empty(&mixer_delayed_put_head)) { | |
| mixer = list_first_entry(&mixer_delayed_put_head, | |
| struct ossp_mixer, delayed_put_link); | |
| if (now <= mixer->put_expires) | |
| break; | |
| assert(mixer->refcnt == 1); | |
| put_mixer_real(mixer); | |
| mixer = NULL; | |
| } | |
| if (mixer) { | |
| ts.tv_sec = mixer->put_expires + 1; | |
| pthread_cond_timedwait(&mixer_delayed_put_cond, &mutex, &ts); | |
| } else | |
| pthread_cond_wait(&mixer_delayed_put_cond, &mutex); | |
| goto again; | |
| } | |
| static void init_mixer_cmd(struct ossp_mixer_cmd *mxcmd, | |
| struct ossp_mixer *mixer) | |
| { | |
| memset(mxcmd, 0, sizeof(*mxcmd)); | |
| memset(&mxcmd->set.vol, -1, sizeof(mxcmd->set.vol)); | |
| mxcmd->mixer = mixer; | |
| mxcmd->out_dir = -1; | |
| } | |
| static int exec_mixer_cmd(struct ossp_mixer_cmd *mxcmd, struct ossp_stream *os) | |
| { | |
| int i, j, rc; | |
| /* | |
| * Set pending flags before trying to execute mixer command. | |
| * Combined with lock release order in exec_cmd(), this | |
| * guarantees that the mixer command will be executed | |
| * immediately or when the current command completes. | |
| */ | |
| pthread_mutex_lock(&mixer_mutex); | |
| os->mixer_pending = 1; | |
| for_each_vol(i, j) | |
| if (mxcmd->set.vol[i][j] >= 0) | |
| os->vol_set[i][j] = mxcmd->set.vol[i][j]; | |
| pthread_mutex_unlock(&mixer_mutex); | |
| rc = exec_simple_cmd(os, OSSP_MIXER, NULL, NULL); | |
| if (rc >= 0) { | |
| dbg0_os(os, "volume set=%d/%d:%d/%d get=%d/%d:%d/%d", | |
| mxcmd->set.vol[PLAY][LEFT], mxcmd->set.vol[PLAY][RIGHT], | |
| mxcmd->set.vol[REC][LEFT], mxcmd->set.vol[REC][RIGHT], | |
| os->vol[PLAY][LEFT], os->vol[PLAY][RIGHT], | |
| os->vol[REC][LEFT], os->vol[REC][RIGHT]); | |
| } else if (rc != -EBUSY) | |
| warn_ose(os, rc, "mixer command failed"); | |
| return rc; | |
| } | |
| static void finish_mixer_cmd(struct ossp_mixer_cmd *mxcmd) | |
| { | |
| struct ossp_mixer *mixer = mxcmd->mixer; | |
| struct ossp_stream *os; | |
| int dir = mxcmd->out_dir; | |
| int vol[2][2] = { }; | |
| int cnt[2][2] = { }; | |
| int i, j; | |
| pthread_mutex_lock(&mixer_mutex); | |
| /* get volume of all streams attached to this mixer */ | |
| pthread_mutex_lock(&mutex); | |
| list_for_each_entry(os, os_pgrp_tbl_head(mixer->pgrp), pgrp_link) { | |
| if (os->pgrp != mixer->pgrp) | |
| continue; | |
| for_each_vol(i, j) { | |
| if (os->vol[i][j] < 0) | |
| continue; | |
| vol[i][j] += os->vol[i][j]; | |
| cnt[i][j]++; | |
| } | |
| } | |
| pthread_mutex_unlock(&mutex); | |
| /* calculate the summary volume values */ | |
| for_each_vol(i, j) { | |
| if (mxcmd->set.vol[i][j] >= 0) | |
| vol[i][j] = mxcmd->set.vol[i][j]; | |
| else if (cnt[i][j]) | |
| vol[i][j] = vol[i][j] / cnt[i][j]; | |
| else if (mixer->vol[i][j] >= 0) | |
| vol[i][j] = mixer->vol[i][j]; | |
| else | |
| vol[i][j] = 100; | |
| vol[i][j] = min(max(0, vol[i][j]), 100); | |
| } | |
| if (dir >= 0) | |
| mxcmd->rvol = vol[dir][LEFT] | (vol[dir][RIGHT] << 8); | |
| pthread_mutex_unlock(&mixer_mutex); | |
| } | |
| static void mixer_simple_ioctl(fuse_req_t req, struct ossp_mixer *mixer, | |
| unsigned cmd, void *uarg, const void *in_buf, | |
| size_t in_bufsz, size_t out_bufsz, | |
| int *not_minep) | |
| { | |
| const char *id = "OSS Proxy", *name = "Mixer"; | |
| int i; | |
| switch (cmd) { | |
| case SOUND_MIXER_INFO: { | |
| struct mixer_info info = { }; | |
| PREP_UARG(NULL, &info); | |
| strncpy(info.id, id, sizeof(info.id) - 1); | |
| strncpy(info.name, name, sizeof(info.name) - 1); | |
| info.modify_counter = mixer->modify_counter; | |
| fuse_reply_ioctl(req, 0, &info, sizeof(info)); | |
| break; | |
| } | |
| case SOUND_OLD_MIXER_INFO: { | |
| struct _old_mixer_info info = { }; | |
| PREP_UARG(NULL, &info); | |
| strncpy(info.id, id, sizeof(info.id) - 1); | |
| strncpy(info.name, name, sizeof(info.name) - 1); | |
| fuse_reply_ioctl(req, 0, &info, sizeof(info)); | |
| break; | |
| } | |
| case OSS_GETVERSION: | |
| i = SNDRV_OSS_VERSION; | |
| goto puti; | |
| case SOUND_MIXER_READ_DEVMASK: | |
| case SOUND_MIXER_READ_STEREODEVS: | |
| i = SOUND_MASK_PCM | SOUND_MASK_IGAIN; | |
| goto puti; | |
| case SOUND_MIXER_READ_CAPS: | |
| i = SOUND_CAP_EXCL_INPUT; | |
| goto puti; | |
| case SOUND_MIXER_READ_RECMASK: | |
| case SOUND_MIXER_READ_RECSRC: | |
| i = SOUND_MASK_IGAIN; | |
| goto puti; | |
| puti: | |
| PREP_UARG(NULL, &i); | |
| fuse_reply_ioctl(req, 0, &i, sizeof(i)); | |
| break; | |
| case SOUND_MIXER_WRITE_RECSRC: | |
| fuse_reply_ioctl(req, 0, NULL, 0); | |
| break; | |
| default: | |
| *not_minep = 1; | |
| } | |
| } | |
| static void mixer_do_ioctl(fuse_req_t req, struct ossp_mixer *mixer, | |
| unsigned cmd, void *uarg, const void *in_buf, | |
| size_t in_bufsz, size_t out_bufsz) | |
| { | |
| struct ossp_mixer_cmd mxcmd; | |
| struct ossp_stream *os, **osa; | |
| int not_mine = 0; | |
| int slot = cmd & 0xff, dir; | |
| int nr_os; | |
| int i, rc; | |
| mixer_simple_ioctl(req, mixer, cmd, uarg, in_buf, in_bufsz, out_bufsz, | |
| ¬_mine); | |
| if (!not_mine) | |
| return; | |
| rc = -ENXIO; | |
| if (!(cmd & (SIOC_IN | SIOC_OUT))) | |
| goto err; | |
| /* | |
| * Okay, it's not one of the easy ones. Build mxcmd for | |
| * actual volume control. | |
| */ | |
| if (cmd & SIOC_IN) | |
| PREP_UARG(&i, &i); | |
| else | |
| PREP_UARG(NULL, &i); | |
| switch (slot) { | |
| case SOUND_MIXER_PCM: | |
| dir = PLAY; | |
| break; | |
| case SOUND_MIXER_IGAIN: | |
| dir = REC; | |
| break; | |
| default: | |
| i = 0; | |
| fuse_reply_ioctl(req, 0, &i, sizeof(i)); | |
| return; | |
| } | |
| init_mixer_cmd(&mxcmd, mixer); | |
| if (cmd & SIOC_IN) { | |
| unsigned l, r; | |
| rc = -EINVAL; | |
| l = i & 0xff; | |
| r = (i >> 8) & 0xff; | |
| if (l > 100 || r > 100) | |
| goto err; | |
| mixer->vol[dir][LEFT] = mxcmd.set.vol[dir][LEFT] = l; | |
| mixer->vol[dir][RIGHT] = mxcmd.set.vol[dir][RIGHT] = r; | |
| } | |
| mxcmd.out_dir = dir; | |
| /* | |
| * Apply volume conrol | |
| */ | |
| /* acquire target streams */ | |
| pthread_mutex_lock(&mutex); | |
| osa = calloc(max_streams, sizeof(osa[0])); | |
| if (!osa) { | |
| pthread_mutex_unlock(&mutex); | |
| rc = -ENOMEM; | |
| goto err; | |
| } | |
| nr_os = 0; | |
| list_for_each_entry(os, os_pgrp_tbl_head(mixer->pgrp), pgrp_link) { | |
| if (os->pgrp == mixer->pgrp) { | |
| osa[nr_os++] = os; | |
| os->refcnt++; | |
| } | |
| } | |
| pthread_mutex_unlock(&mutex); | |
| /* execute mxcmd for each stream and put it */ | |
| for (i = 0; i < nr_os; i++) { | |
| exec_mixer_cmd(&mxcmd, osa[i]); | |
| put_os(osa[i]); | |
| } | |
| finish_mixer_cmd(&mxcmd); | |
| free(osa); | |
| if (out_bufsz) | |
| fuse_reply_ioctl(req, 0, &mxcmd.rvol, sizeof(mxcmd.rvol)); | |
| else | |
| fuse_reply_ioctl(req, 0, NULL, 0); | |
| return; | |
| err: | |
| fuse_reply_err(req, -rc); | |
| } | |
| static void mixer_open(fuse_req_t req, struct fuse_file_info *fi) | |
| { | |
| pid_t pid = fuse_req_ctx(req)->pid, pgrp; | |
| struct ossp_mixer *mixer; | |
| int rc; | |
| rc = get_proc_self_info(pid, &pgrp, NULL, 0); | |
| if (rc) { | |
| err_e(rc, "get_proc_self_info(%d) failed", pid); | |
| fuse_reply_err(req, -rc); | |
| return; | |
| } | |
| mixer = get_mixer(pgrp); | |
| fi->fh = pgrp; | |
| if (mixer) | |
| fuse_reply_open(req, fi); | |
| else | |
| fuse_reply_err(req, ENOMEM); | |
| } | |
| static void mixer_ioctl(fuse_req_t req, int signed_cmd, void *uarg, | |
| struct fuse_file_info *fi, unsigned int flags, | |
| const void *in_buf, size_t in_bufsz, size_t out_bufsz) | |
| { | |
| struct ossp_mixer *mixer; | |
| mixer = find_mixer(fi->fh); | |
| if (!mixer) { | |
| fuse_reply_err(req, EBADF); | |
| return; | |
| } | |
| mixer_do_ioctl(req, mixer, signed_cmd, uarg, in_buf, in_bufsz, | |
| out_bufsz); | |
| } | |
| static void mixer_release(fuse_req_t req, struct fuse_file_info *fi) | |
| { | |
| struct ossp_mixer *mixer; | |
| mixer = find_mixer(fi->fh); | |
| if (mixer) { | |
| put_mixer(mixer); | |
| fuse_reply_err(req, 0); | |
| } else | |
| fuse_reply_err(req, EBADF); | |
| } | |
| /*************************************************************************** | |
| * Stream implementation | |
| */ | |
| static int os_create_shared_memory(struct ossp_stream *os, size_t mmap_size) | |
| { | |
| int rc; | |
| os->mmap_fd = -1; | |
| os->mmap_size = 0; | |
| #ifdef OSSP_MMAP | |
| if (mmap_size) { | |
| char shmname[32]; | |
| int fd; | |
| void *p; | |
| sprintf(shmname, "/ossp.%i", getpid()); | |
| fd = shm_open(shmname, O_RDWR | O_CREAT | O_EXCL | O_TRUNC, | |
| 0600); | |
| if (fd == -1) { | |
| rc = -errno; | |
| warn_ose(os, rc, "failed to open shared memory"); | |
| return rc; | |
| } | |
| rc = shm_unlink(shmname); | |
| if (rc == -1) { | |
| rc = -errno; | |
| close(fd); | |
| warn_ose(os, rc, "failed to unlink shared memory"); | |
| return rc; | |
| } | |
| rc = ftruncate(fd, mmap_size + 2*sizeof(struct ossp_transfer)); | |
| if (rc == -1) { | |
| rc = -errno; | |
| close(fd); | |
| warn_ose(os, rc, "failed to set shared memory size"); | |
| return rc; | |
| } | |
| rc = fcntl(fd, F_SETFD, 0); /* reset cloexec */ | |
| if (rc == -1) { | |
| rc = -errno; | |
| close(fd); | |
| warn_ose(os, rc, "failed to reset close-on-exec for shared memory"); | |
| return rc; | |
| } | |
| p = mmap(NULL, mmap_size + 2 * sizeof(struct ossp_transfer), | |
| PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); | |
| if (p == MAP_FAILED) { | |
| rc = -errno; | |
| close(fd); | |
| warn_ose(os, rc, "failed to mmap shared memory"); | |
| return rc; | |
| } | |
| os->mmap_addr[PLAY] = p; | |
| os->mmap_addr[REC] = p + mmap_size / 2; | |
| os->mmap_transfer = p + mmap_size; | |
| if (sem_init(&os->mmap_transfer[PLAY].sem, 1, 0) == -1 || | |
| sem_init(&os->mmap_transfer[REC].sem, 1, 0) == -1) { | |
| rc = -errno; | |
| close(fd); | |
| munmap(p, mmap_size + 2 * sizeof(struct ossp_transfer)); | |
| warn_ose(os, rc, "failed to init shared semaphore"); | |
| return rc; | |
| } | |
| os->mmap = p; | |
| os->mmap_size = mmap_size; | |
| os->mmap_fd = fd; | |
| #endif | |
| } | |
| return 0; | |
| } | |
| static int alloc_os(size_t stream_size, size_t mmap_size, pid_t pid, uid_t pgrp, | |
| uid_t uid, gid_t gid, int cmd_sock, | |
| const int *notify, struct fuse_session *se, | |
| struct ossp_stream **osp) | |
| { | |
| struct ossp_uid_cnt *tmp_ucnt, *ucnt = NULL; | |
| struct ossp_stream *os; | |
| int rc; | |
| assert(stream_size >= sizeof(struct ossp_stream)); | |
| os = calloc(1, stream_size); | |
| if (!os) | |
| return -ENOMEM; | |
| INIT_LIST_HEAD(&os->link); | |
| INIT_LIST_HEAD(&os->pgrp_link); | |
| INIT_LIST_HEAD(&os->notify_link); | |
| os->refcnt = 1; | |
| rc = -pthread_mutex_init(&os->cmd_mutex, NULL); | |
| if (rc) | |
| goto err_free; | |
| rc = -pthread_mutex_init(&os->mmap_mutex, NULL); | |
| if (rc) | |
| goto err_destroy_cmd_mutex; | |
| pthread_mutex_lock(&mutex); | |
| list_for_each_entry(tmp_ucnt, &uid_cnt_list, link) | |
| if (tmp_ucnt->uid == uid) { | |
| ucnt = tmp_ucnt; | |
| break; | |
| } | |
| if (!ucnt) { | |
| rc = -ENOMEM; | |
| ucnt = calloc(1, sizeof(*ucnt)); | |
| if (!ucnt) | |
| goto err_unlock; | |
| ucnt->uid = uid; | |
| list_add(&ucnt->link, &uid_cnt_list); | |
| } | |
| rc = -EBUSY; | |
| if (ucnt->nr_os + 1 > umax_streams) | |
| goto err_unlock; | |
| /* everything looks fine, allocate id and init stream */ | |
| rc = -EBUSY; | |
| os->id = find_next_zero_bit(os_id_bitmap, max_streams, 0); | |
| if (os->id >= max_streams) | |
| goto err_unlock; | |
| __set_bit(os->id, os_id_bitmap); | |
| os->cmd_fd = cmd_sock; | |
| os->notify_tx = notify[1]; | |
| os->notify_rx = notify[0]; | |
| os->pid = pid; | |
| os->pgrp = pgrp; | |
| os->uid = uid; | |
| os->gid = gid; | |
| os->ucnt = ucnt; | |
| os->se = se; | |
| rc = os_create_shared_memory(os, mmap_size); | |
| if (rc) | |
| goto err_unlock; | |
| memset(os->vol, -1, sizeof(os->vol)); | |
| memset(os->vol_set, -1, sizeof(os->vol)); | |
| list_add(&os->link, os_tbl_head(os->id)); | |
| list_add(&os->pgrp_link, os_pgrp_tbl_head(os->pgrp)); | |
| ucnt->nr_os++; | |
| *osp = os; | |
| pthread_mutex_unlock(&mutex); | |
| return 0; | |
| err_unlock: | |
| pthread_mutex_unlock(&mutex); | |
| pthread_mutex_destroy(&os->mmap_mutex); | |
| err_destroy_cmd_mutex: | |
| pthread_mutex_destroy(&os->cmd_mutex); | |
| err_free: | |
| free(os); | |
| return rc; | |
| } | |
| static void shutdown_notification(struct ossp_stream *os) | |
| { | |
| struct ossp_notify obituary = { .magic = OSSP_NOTIFY_MAGIC, | |
| .opcode = OSSP_NOTIFY_OBITUARY }; | |
| ssize_t ret; | |
| /* | |
| * Shutdown notification for this stream. We politely ask | |
| * notify_poller to shut the receive side down to avoid racing | |
| * with it. | |
| */ | |
| while (os->notify_rx >= 0) { | |
| ret = write(os->notify_tx, &obituary, sizeof(obituary)); | |
| if (ret <= 0) { | |
| if (ret == 0) | |
| warn_os(os, "unexpected EOF on notify_tx"); | |
| else if (errno != EPIPE) | |
| warn_ose(os, -errno, | |
| "unexpected error on notify_tx"); | |
| close(os->notify_rx); | |
| os->notify_rx = -1; | |
| break; | |
| } | |
| if (ret != sizeof(obituary)) | |
| warn_os(os, "short transfer on notify_tx"); | |
| pthread_cond_wait(¬ify_poller_kill_wait, &mutex); | |
| } | |
| } | |
| static void put_os(struct ossp_stream *os) | |
| { | |
| if (!os) | |
| return; | |
| pthread_mutex_lock(&mutex); | |
| assert(os->refcnt); | |
| if (--os->refcnt) { | |
| pthread_mutex_unlock(&mutex); | |
| return; | |
| } | |
| os->dead = 1; | |
| shutdown_notification(os); | |
| dbg0_os(os, "DESTROY"); | |
| list_del_init(&os->link); | |
| list_del_init(&os->pgrp_link); | |
| list_del_init(&os->notify_link); | |
| os->ucnt->nr_os--; | |
| pthread_mutex_unlock(&mutex); | |
| close(os->cmd_fd); | |
| close(os->notify_tx); | |
| put_mixer(os->mixer); | |
| pthread_mutex_destroy(&os->cmd_mutex); | |
| pthread_mutex_destroy(&os->mmap_mutex); | |
| pthread_mutex_lock(&mutex); | |
| dbg1_os(os, "stream dead, requesting reaping"); | |
| list_add_tail(&os->link, &slave_corpse_list); | |
| pthread_cond_signal(&slave_reaper_wait); | |
| pthread_mutex_unlock(&mutex); | |
| } | |
| static void set_extra_env(pid_t pid) | |
| { | |
| char procenviron[32]; | |
| const int step = 1024; | |
| char *data = malloc(step + 1); | |
| int ofs = 0; | |
| int fd; | |
| int ret; | |
| if (!data) | |
| return; | |
| sprintf(procenviron, "/proc/%d/environ", pid); | |
| fd = open(procenviron, O_RDONLY); | |
| if (fd < 0) | |
| return; | |
| /* | |
| * There should really be a 'read whole file to a newly allocated | |
| * buffer' function. | |
| */ | |
| while ((ret = read(fd, data + ofs, step)) > 0) { | |
| char *newdata; | |
| ofs += ret; | |
| newdata = realloc(data, ofs + step + 1); | |
| if (!newdata) { | |
| ret = -1; | |
| break; | |
| } | |
| data = newdata; | |
| } | |
| if (ret == 0) { | |
| char *ptr = data; | |
| /* Append the extra 0 for end condition */ | |
| data[ofs] = 0; | |
| while ((ret = strlen(ptr)) > 0) { | |
| /* | |
| * Copy all PULSE variables and DISPLAY so that | |
| * ssh -X remotehost 'mplayer -ao oss' will work | |
| */ | |
| if (!strncmp(ptr, "DISPLAY=", 8) || | |
| !strncmp(ptr, "PULSE_", 6)) | |
| putenv(ptr); | |
| ptr += ret + 1; | |
| } | |
| } | |
| free(data); | |
| close(fd); | |
| } | |
| static int create_os(const char *slave_path, | |
| size_t stream_size, size_t mmap_size, | |
| pid_t pid, pid_t pgrp, uid_t uid, gid_t gid, | |
| struct fuse_session *se, struct ossp_stream **osp) | |
| { | |
| static pthread_mutex_t create_mutex = PTHREAD_MUTEX_INITIALIZER; | |
| int cmd_sock[2] = { -1, -1 }; | |
| int notify_sock[2] = { -1, -1 }; | |
| struct ossp_stream *os = NULL; | |
| struct epoll_event ev = { }; | |
| int i, rc; | |
| /* | |
| * Only one thread can be creating a stream. This is to avoid | |
| * leaking unwanted fds into slaves. | |
| */ | |
| pthread_mutex_lock(&create_mutex); | |
| /* prepare communication channels */ | |
| if (socketpair(AF_UNIX, SOCK_STREAM, 0, cmd_sock) || | |
| socketpair(AF_UNIX, SOCK_STREAM, 0, notify_sock)) { | |
| rc = -errno; | |
| warn_e(rc, "failed to create slave command channel"); | |
| goto close_all; | |
| } | |
| if (fcntl(notify_sock[0], F_SETFL, O_NONBLOCK) < 0) { | |
| rc = -errno; | |
| warn_e(rc, "failed to set NONBLOCK on notify sock"); | |
| goto close_all; | |
| } | |
| /* | |
| * Alloc stream which will be responsible for all server side | |
| * resources from now on. | |
| */ | |
| rc = alloc_os(stream_size, mmap_size, pid, pgrp, uid, gid, cmd_sock[0], | |
| notify_sock, se, &os); | |
| if (rc) { | |
| warn_e(rc, "failed to allocate stream for %d", pid); | |
| goto close_all; | |
| } | |
| rc = -ENOMEM; | |
| os->mixer = get_mixer(pgrp); | |
| if (!os->mixer) | |
| goto put_os; | |
| /* | |
| * Register notification. If successful, notify_poller has | |
| * custody of notify_rx fd. | |
| */ | |
| pthread_mutex_lock(&mutex); | |
| list_add(&os->notify_link, os_notify_tbl_head(os->notify_rx)); | |
| pthread_mutex_unlock(&mutex); | |
| ev.events = EPOLLIN; | |
| ev.data.fd = notify_sock[0]; | |
| if (epoll_ctl(notify_epfd, EPOLL_CTL_ADD, notify_sock[0], &ev)) { | |
| /* | |
| * Without poller watching this notify sock, poller | |
| * shutdown sequence in shutdown_notification() can't | |
| * be used. Kill notification rx manually. | |
| */ | |
| rc = -errno; | |
| warn_ose(os, rc, "failed to add notify epoll"); | |
| close(os->notify_rx); | |
| os->notify_rx = -1; | |
| goto put_os; | |
| } | |
| /* start slave */ | |
| os->slave_pid = fork(); | |
| if (os->slave_pid < 0) { | |
| rc = -errno; | |
| warn_ose(os, rc, "failed to fork slave"); | |
| goto put_os; | |
| } | |
| if (os->slave_pid == 0) { | |
| /* child */ | |
| char id_str[2][16], fd_str[3][16]; | |
| char mmap_size_str[32]; | |
| char log_str[16], slave_path_copy[PATH_MAX]; | |
| char *argv[] = { slave_path_copy, "-u", id_str[0], | |
| "-g", id_str[1], "-c", fd_str[0], | |
| "-n", fd_str[1], "-m", fd_str[2], | |
| "-s", mmap_size_str, | |
| "-l", log_str, NULL, NULL }; | |
| struct passwd *pwd; | |
| /* drop stuff we don't need */ | |
| if (close(cmd_sock[0]) || close(notify_sock[0])) | |
| fatal_e(-errno, "failed to close server pipe fds"); | |
| clearenv(); | |
| pwd = getpwuid(os->uid); | |
| if (pwd) { | |
| setenv("LOGNAME", pwd->pw_name, 1); | |
| setenv("USER", pwd->pw_name, 1); | |
| setenv("HOME", pwd->pw_dir, 1); | |
| } | |
| /* Set extra environment variables from the caller */ | |
| set_extra_env(pid); | |
| /* prep and exec */ | |
| slave_path_copy[sizeof(slave_path_copy) - 1] = '\0'; | |
| strncpy(slave_path_copy, slave_path, sizeof(slave_path_copy) - 1); | |
| if (slave_path_copy[sizeof(slave_path_copy) - 1] != '\0') { | |
| rc = -errno; | |
| err_ose(os, rc, "slave path too long"); | |
| goto child_fail; | |
| } | |
| snprintf(id_str[0], sizeof(id_str[0]), "%d", os->uid); | |
| snprintf(id_str[1], sizeof(id_str[0]), "%d", os->gid); | |
| snprintf(fd_str[0], sizeof(fd_str[0]), "%d", cmd_sock[1]); | |
| snprintf(fd_str[1], sizeof(fd_str[1]), "%d", notify_sock[1]); | |
| snprintf(fd_str[2], sizeof(fd_str[2]), "%d", os->mmap_fd); | |
| snprintf(mmap_size_str, sizeof(mmap_size_str), "0x%zx", | |
| os->mmap_size); | |
| snprintf(log_str, sizeof(log_str), "%d", ossp_log_level); | |
| if (ossp_log_timestamp) | |
| argv[ARRAY_SIZE(argv) - 2] = "-t"; | |
| execv(slave_path, argv); | |
| rc = -errno; | |
| err_ose(os, rc, "execv failed for <%d>", pid); | |
| child_fail: | |
| _exit(1); | |
| } | |
| /* turn on CLOEXEC on all server side fds */ | |
| if (fcntl(os->cmd_fd, F_SETFD, FD_CLOEXEC) < 0 || | |
| fcntl(os->notify_tx, F_SETFD, FD_CLOEXEC) < 0 || | |
| fcntl(os->notify_rx, F_SETFD, FD_CLOEXEC) < 0) { | |
| rc = -errno; | |
| err_ose(os, rc, "failed to set CLOEXEC on server side fds"); | |
| goto put_os; | |
| } | |
| dbg0_os(os, "CREATE slave=%d %s", os->slave_pid, slave_path); | |
| dbg0_os(os, " client=%d cmd=%d:%d notify=%d:%d mmap=%d:%zu", | |
| pid, cmd_sock[0], cmd_sock[1], notify_sock[0], notify_sock[1], | |
| os->mmap_fd, os->mmap_size); | |
| close(os->mmap_fd); | |
| os->mmap_fd = -1; | |
| *osp = os; | |
| rc = 0; | |
| goto close_client_fds; | |
| put_os: | |
| put_os(os); | |
| close_client_fds: | |
| close(cmd_sock[1]); | |
| pthread_mutex_unlock(&create_mutex); | |
| return rc; | |
| close_all: | |
| for (i = 0; i < 2; i++) { | |
| close(cmd_sock[i]); | |
| close(notify_sock[i]); | |
| } | |
| pthread_mutex_unlock(&create_mutex); | |
| return rc; | |
| } | |
| static void dsp_open_common(fuse_req_t req, struct fuse_file_info *fi, | |
| struct fuse_session *se) | |
| { | |
| const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req); | |
| struct ossp_dsp_open_arg arg = { }; | |
| struct ossp_stream *os = NULL; | |
| struct ossp_mixer *mixer; | |
| struct ossp_dsp_stream *dsps; | |
| struct ossp_mixer_cmd mxcmd; | |
| pid_t pgrp; | |
| ssize_t ret; | |
| ret = get_proc_self_info(fuse_ctx->pid, &pgrp, NULL, 0); | |
| if (ret) { | |
| err_e(ret, "get_proc_self_info(%d) failed", fuse_ctx->pid); | |
| goto err; | |
| } | |
| ret = create_os(dsp_slave_path, sizeof(*dsps), DSPS_MMAP_SIZE, | |
| fuse_ctx->pid, pgrp, fuse_ctx->uid, fuse_ctx->gid, | |
| se, &os); | |
| if (ret) | |
| goto err; | |
| dsps = os_to_dsps(os); | |
| mixer = os->mixer; | |
| switch (fi->flags & O_ACCMODE) { | |
| case O_WRONLY: | |
| dsps->rw |= 1 << PLAY; | |
| break; | |
| case O_RDONLY: | |
| dsps->rw |= 1 << REC; | |
| break; | |
| case O_RDWR: | |
| dsps->rw |= (1 << PLAY) | (1 << REC); | |
| break; | |
| default: | |
| assert(0); | |
| } | |
| arg.flags = fi->flags; | |
| arg.opener_pid = os->pid; | |
| ret = exec_simple_cmd(&dsps->os, OSSP_DSP_OPEN, &arg, NULL); | |
| if (ret < 0) { | |
| put_os(os); | |
| goto err; | |
| } | |
| memcpy(os->vol, mixer->vol, sizeof(os->vol)); | |
| if (os->vol[PLAY][0] >= 0 || os->vol[REC][0] >= 0) { | |
| init_mixer_cmd(&mxcmd, mixer); | |
| memcpy(mxcmd.set.vol, os->vol, sizeof(os->vol)); | |
| exec_mixer_cmd(&mxcmd, os); | |
| finish_mixer_cmd(&mxcmd); | |
| } | |
| fi->direct_io = 1; | |
| fi->nonseekable = 1; | |
| fi->fh = os->id; | |
| fuse_reply_open(req, fi); | |
| return; | |
| err: | |
| fuse_reply_err(req, -ret); | |
| } | |
| static void dsp_open(fuse_req_t req, struct fuse_file_info *fi) | |
| { | |
| dsp_open_common(req, fi, dsp_se); | |
| } | |
| static void adsp_open(fuse_req_t req, struct fuse_file_info *fi) | |
| { | |
| dsp_open_common(req, fi, adsp_se); | |
| } | |
| static void dsp_release(fuse_req_t req, struct fuse_file_info *fi) | |
| { | |
| struct ossp_stream *os; | |
| os = find_os(fi->fh); | |
| if (os) { | |
| put_os(os); | |
| fuse_reply_err(req, 0); | |
| } else | |
| fuse_reply_err(req, EBADF); | |
| } | |
| static void dsp_read(fuse_req_t req, size_t size, off_t off, | |
| struct fuse_file_info *fi) | |
| { | |
| struct ossp_dsp_rw_arg arg = { }; | |
| struct ossp_stream *os; | |
| struct ossp_dsp_stream *dsps; | |
| void *buf = NULL; | |
| ssize_t ret; | |
| ret = -EBADF; | |
| os = find_os(fi->fh); | |
| if (!os) | |
| goto out; | |
| dsps = os_to_dsps(os); | |
| ret = -EINVAL; | |
| if (!(dsps->rw & (1 << REC))) | |
| goto out; | |
| ret = -ENXIO; | |
| if (dsps->mmapped) | |
| goto out; | |
| ret = -ENOMEM; | |
| buf = malloc(size); | |
| if (!buf) | |
| goto out; | |
| arg.nonblock = (fi->flags & O_NONBLOCK) || dsps->nonblock; | |
| ret = exec_cmd(os, OSSP_DSP_READ, &arg, sizeof(arg), | |
| NULL, 0, NULL, 0, buf, &size, -1); | |
| out: | |
| if (ret >= 0) | |
| fuse_reply_buf(req, buf, size); | |
| else | |
| fuse_reply_err(req, -ret); | |
| free(buf); | |
| } | |
| static void dsp_write(fuse_req_t req, const char *buf, size_t size, off_t off, | |
| struct fuse_file_info *fi) | |
| { | |
| struct ossp_dsp_rw_arg arg = { }; | |
| struct ossp_stream *os; | |
| struct ossp_dsp_stream *dsps; | |
| ssize_t ret; | |
| ret = -EBADF; | |
| os = find_os(fi->fh); | |
| if (!os) | |
| goto out; | |
| dsps = os_to_dsps(os); | |
| ret = -EINVAL; | |
| if (!(dsps->rw & (1 << PLAY))) | |
| goto out; | |
| ret = -ENXIO; | |
| if (dsps->mmapped) | |
| goto out; | |
| arg.nonblock = (fi->flags & O_NONBLOCK) || dsps->nonblock; | |
| ret = exec_cmd(os, OSSP_DSP_WRITE, &arg, sizeof(arg), | |
| buf, size, NULL, 0, NULL, NULL, -1); | |
| out: | |
| if (ret >= 0) | |
| fuse_reply_write(req, ret); | |
| else | |
| fuse_reply_err(req, -ret); | |
| } | |
| static void dsp_poll(fuse_req_t req, struct fuse_file_info *fi, | |
| struct fuse_pollhandle *ph) | |
| { | |
| int notify = ph != NULL; | |
| unsigned revents = 0; | |
| struct ossp_stream *os; | |
| ssize_t ret; | |
| ret = -EBADF; | |
| os = find_os(fi->fh); | |
| if (!os) | |
| goto out; | |
| if (ph) { | |
| pthread_mutex_lock(&mutex); | |
| if (os->ph) | |
| fuse_pollhandle_destroy(os->ph); | |
| os->ph = ph; | |
| pthread_mutex_unlock(&mutex); | |
| } | |
| ret = exec_simple_cmd(os, OSSP_DSP_POLL, ¬ify, &revents); | |
| out: | |
| if (ret >= 0) | |
| fuse_reply_poll(req, revents); | |
| else | |
| fuse_reply_err(req, -ret); | |
| } | |
| static void dsp_ioctl(fuse_req_t req, int signed_cmd, void *uarg, | |
| struct fuse_file_info *fi, unsigned int flags, | |
| const void *in_buf, size_t in_bufsz, size_t out_bufsz) | |
| { | |
| /* some ioctl constants are long and has the highest bit set */ | |
| unsigned cmd = signed_cmd; | |
| struct ossp_stream *os; | |
| struct ossp_dsp_stream *dsps; | |
| enum ossp_opcode op; | |
| ssize_t ret; | |
| int i; | |
| ret = -EBADF; | |
| os = find_os(fi->fh); | |
| if (!os) | |
| goto err; | |
| dsps = os_to_dsps(os); | |
| /* mixer commands are allowed on DSP devices */ | |
| if (((cmd >> 8) & 0xff) == 'M') { | |
| mixer_do_ioctl(req, os->mixer, cmd, uarg, in_buf, in_bufsz, | |
| out_bufsz); | |
| return; | |
| } | |
| /* and the rest */ | |
| switch (cmd) { | |
| case OSS_GETVERSION: | |
| i = SNDRV_OSS_VERSION; | |
| PREP_UARG(NULL, &i); | |
| fuse_reply_ioctl(req, 0, &i, sizeof(i)); | |
| break; | |
| case SNDCTL_DSP_GETCAPS: | |
| i = DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER | | |
| #ifdef OSSP_MMAP | |
| DSP_CAP_MMAP | | |
| #endif | |
| DSP_CAP_MULTI; | |
| PREP_UARG(NULL, &i); | |
| fuse_reply_ioctl(req, 0, &i, sizeof(i)); | |
| break; | |
| case SNDCTL_DSP_NONBLOCK: | |
| dsps->nonblock = 1; | |
| ret = 0; | |
| fuse_reply_ioctl(req, 0, NULL, 0); | |
| break; | |
| case SNDCTL_DSP_RESET: op = OSSP_DSP_RESET; goto nd; | |
| case SNDCTL_DSP_SYNC: op = OSSP_DSP_SYNC; goto nd; | |
| case SNDCTL_DSP_POST: op = OSSP_DSP_POST; goto nd; | |
| nd: | |
| ret = exec_simple_cmd(&dsps->os, op, NULL, NULL); | |
| if (ret) | |
| goto err; | |
| fuse_reply_ioctl(req, 0, NULL, 0); | |
| break; | |
| case SOUND_PCM_READ_RATE: op = OSSP_DSP_GET_RATE; goto ri; | |
| case SOUND_PCM_READ_BITS: op = OSSP_DSP_GET_FORMAT; goto ri; | |
| case SOUND_PCM_READ_CHANNELS: op = OSSP_DSP_GET_CHANNELS; goto ri; | |
| case SNDCTL_DSP_GETBLKSIZE: op = OSSP_DSP_GET_BLKSIZE; goto ri; | |
| case SNDCTL_DSP_GETFMTS: op = OSSP_DSP_GET_FORMATS; goto ri; | |
| case SNDCTL_DSP_GETTRIGGER: op = OSSP_DSP_GET_TRIGGER; goto ri; | |
| ri: | |
| PREP_UARG(NULL, &i); | |
| ret = exec_simple_cmd(&dsps->os, op, NULL, &i); | |
| if (ret) | |
| goto err; | |
| fuse_reply_ioctl(req, 0, &i, sizeof(i)); | |
| break; | |
| case SNDCTL_DSP_SPEED: op = OSSP_DSP_SET_RATE; goto wi; | |
| case SNDCTL_DSP_SETFMT: op = OSSP_DSP_SET_FORMAT; goto wi; | |
| case SNDCTL_DSP_CHANNELS: op = OSSP_DSP_SET_CHANNELS; goto wi; | |
| case SNDCTL_DSP_SUBDIVIDE: op = OSSP_DSP_SET_SUBDIVISION; goto wi; | |
| wi: | |
| PREP_UARG(&i, &i); | |
| ret = exec_simple_cmd(&dsps->os, op, &i, &i); | |
| if (ret) | |
| goto err; | |
| fuse_reply_ioctl(req, 0, &i, sizeof(i)); | |
| break; | |
| case SNDCTL_DSP_STEREO: | |
| PREP_UARG(NULL, &i); | |
| i = 2; | |
| ret = exec_simple_cmd(&dsps->os, OSSP_DSP_SET_CHANNELS, &i, &i); | |
| i--; | |
| if (ret) | |
| goto err; | |
| fuse_reply_ioctl(req, 0, &i, sizeof(i)); | |
| break; | |
| case SNDCTL_DSP_SETFRAGMENT: | |
| PREP_UARG(&i, NULL); | |
| ret = exec_simple_cmd(&dsps->os, | |
| OSSP_DSP_SET_FRAGMENT, &i, NULL); | |
| if (ret) | |
| goto err; | |
| fuse_reply_ioctl(req, 0, NULL, 0); | |
| break; | |
| case SNDCTL_DSP_SETTRIGGER: | |
| PREP_UARG(&i, NULL); | |
| ret = exec_simple_cmd(&dsps->os, | |
| OSSP_DSP_SET_TRIGGER, &i, NULL); | |
| if (ret) | |
| goto err; | |
| fuse_reply_ioctl(req, 0, NULL, 0); | |
| break; | |
| case SNDCTL_DSP_GETOSPACE: | |
| case SNDCTL_DSP_GETISPACE: { | |
| struct audio_buf_info info; | |
| ret = -EINVAL; | |
| if (cmd == SNDCTL_DSP_GETOSPACE) { | |
| if (!(dsps->rw & (1 << PLAY))) | |
| goto err; | |
| op = OSSP_DSP_GET_OSPACE; | |
| } else { | |
| if (!(dsps->rw & (1 << REC))) | |
| goto err; | |
| op = OSSP_DSP_GET_ISPACE; | |
| } | |
| PREP_UARG(NULL, &info); | |
| ret = exec_simple_cmd(&dsps->os, op, NULL, &info); | |
| if (ret) | |
| goto err; | |
| fuse_reply_ioctl(req, 0, &info, sizeof(info)); | |
| break; | |
| } | |
| case SNDCTL_DSP_GETOPTR: | |
| case SNDCTL_DSP_GETIPTR: { | |
| struct count_info info; | |
| op = cmd == SNDCTL_DSP_GETOPTR ? OSSP_DSP_GET_OPTR | |
| : OSSP_DSP_GET_IPTR; | |
| PREP_UARG(NULL, &info); | |
| ret = exec_simple_cmd(&dsps->os, op, NULL, &info); | |
| if (ret) | |
| goto err; | |
| fuse_reply_ioctl(req, 0, &info, sizeof(info)); | |
| break; | |
| } | |
| case SNDCTL_DSP_GETODELAY: | |
| PREP_UARG(NULL, &i); | |
| i = 0; | |
| ret = exec_simple_cmd(&dsps->os, OSSP_DSP_GET_ODELAY, NULL, &i); | |
| fuse_reply_ioctl(req, ret, &i, sizeof(i)); /* always copy out result, 0 on err */ | |
| break; | |
| case SOUND_PCM_WRITE_FILTER: | |
| case SOUND_PCM_READ_FILTER: | |
| ret = -EIO; | |
| goto err; | |
| case SNDCTL_DSP_MAPINBUF: | |
| case SNDCTL_DSP_MAPOUTBUF: | |
| ret = -EINVAL; | |
| goto err; | |
| case SNDCTL_DSP_SETSYNCRO: | |
| case SNDCTL_DSP_SETDUPLEX: | |
| case SNDCTL_DSP_PROFILE: | |
| fuse_reply_ioctl(req, 0, NULL, 0); | |
| break; | |
| default: | |
| warn_os(os, "unknown ioctl 0x%x", cmd); | |
| ret = -EINVAL; | |
| goto err; | |
| } | |
| return; | |
| err: | |
| fuse_reply_err(req, -ret); | |
| } | |
| #ifdef OSSP_MMAP | |
| static int dsp_mmap_dir(int prot) | |
| { | |
| if (!(prot & PROT_WRITE)) | |
| return REC; | |
| return PLAY; | |
| } | |
| static int dsp_dir_to_mapid(struct ossp_stream *os, int dir) | |
| { | |
| return os->id * 2 + dir + 1; | |
| } | |
| static int dsp_mapid_to_dir(int map_id) | |
| { | |
| return (map_id - 1) & 1; | |
| } | |
| static void dsp_mmap(fuse_req_t req, uint64_t addr, size_t len, int prot, | |
| int flags, off_t offset, struct fuse_file_info *fi) | |
| { | |
| int dir = dsp_mmap_dir(prot); | |
| struct ossp_dsp_mmap_arg arg = { }; | |
| struct ossp_stream *os; | |
| struct ossp_dsp_stream *dsps; | |
| ssize_t ret; | |
| os = find_os(fi->fh); | |
| if (!os) { | |
| fuse_reply_err(req, EBADF); | |
| return; | |
| } | |
| dsps = os_to_dsps(os); | |
| if (len > os->mmap_size / 2) { | |
| fuse_reply_err(req, EINVAL); | |
| return; | |
| } | |
| pthread_mutex_lock(&os->mmap_mutex); | |
| ret = -EBUSY; | |
| if (dsps->mmapped & (1 << dir)) | |
| goto out_unlock; | |
| arg.dir = dir; | |
| arg.size = len; | |
| ret = exec_simple_cmd(os, OSSP_DSP_MMAP, &arg, NULL); | |
| if (ret == 0) | |
| dsps->mmapped |= 1 << dir; | |
| out_unlock: | |
| pthread_mutex_unlock(&os->mmap_mutex); | |
| if (ret == 0) | |
| fuse_reply_mmap(req, dsp_dir_to_mapid(os, dir), os->mmap_size / 2); | |
| else | |
| fuse_reply_err(req, -ret); | |
| } | |
| static void dsp_munmap(fuse_req_t req, uint64_t map_id, size_t length, | |
| struct fuse_file_info *fi) | |
| { | |
| struct ossp_stream *os; | |
| struct ossp_dsp_stream *dsps; | |
| int dir, rc; | |
| os = find_os(fi->fh); | |
| if (!os) | |
| goto out; | |
| dsps = os_to_dsps(os); | |
| pthread_mutex_lock(&os->mmap_mutex); | |
| dir = dsp_mapid_to_dir(map_id); | |
| if (dsp_dir_to_mapid(os, dir) != map_id || | |
| (dir != PLAY && dir != REC) || length != os->mmap_size / 2) { | |
| warn_os(os, "invalid munmap request map_id=%llu len=%zu mmapped=0x%x", | |
| (unsigned long long) map_id, length, dsps->mmapped); | |
| goto out_unlock; | |
| } | |
| rc = exec_simple_cmd(os, OSSP_DSP_MUNMAP, &dir, NULL); | |
| if (rc) | |
| warn_ose(os, rc, "MUNMAP failed for dir=%d", dir); | |
| dsps->mmapped &= ~(1 << dir); | |
| out_unlock: | |
| pthread_mutex_unlock(&os->mmap_mutex); | |
| out: | |
| fuse_reply_err(req, -rc); | |
| } | |
| static void fill_mmap_buffer(struct ossp_stream *os) | |
| { | |
| int rc; | |
| struct fuse_chan *ch = fuse_session_next_chan(os->se, NULL); | |
| unsigned mapid = dsp_dir_to_mapid(os, PLAY); | |
| size_t pos = os->mmap_transfer[PLAY].pos; | |
| size_t bytes = os->mmap_transfer[PLAY].bytes; | |
| dbg1_os(os, "fill mmap buffer pos=%zu bytes=%zu", pos, bytes); | |
| rc = fuse_lowlevel_notify_retrieve(ch, mapid, bytes, pos, os); | |
| if (rc) | |
| sem_post(&os->mmap_transfer[PLAY].sem); | |
| } | |
| static void dsp_retrieve_reply(fuse_req_t req, void *cookie, uint64_t map_id, | |
| off_t offset, struct fuse_bufvec *bufv) | |
| { | |
| struct ossp_stream *os = cookie; | |
| size_t size = fuse_buf_size(bufv); | |
| struct fuse_bufvec dest = FUSE_BUFVEC_INIT(size); | |
| ssize_t res; | |
| if (dsp_mapid_to_dir(map_id) != PLAY || | |
| dsp_dir_to_mapid(os, PLAY) != map_id || | |
| offset != os->mmap_transfer[PLAY].pos || | |
| size > os->mmap_transfer[PLAY].bytes) { | |
| warn_os(os, "invalid retrieve reply map_id=%llu offset=%llu, size=%zu", | |
| (unsigned long long) map_id, | |
| (unsigned long long) offset, size); | |
| sem_post(&os->mmap_transfer[PLAY].sem); | |
| return; | |
| } | |
| dest.buf[0].mem = os->mmap_addr[PLAY] + offset; | |
| res = fuse_buf_copy(&dest, bufv, 0); | |
| if (res < 0) | |
| warn_ose(os, res, "dsp_retrieve_reply: buffer copy"); | |
| else if ((size_t) res < size) { | |
| warn_os(os, "dsp_retrieve_reply: short buffer copy"); | |
| } | |
| if (size < os->mmap_transfer[PLAY].bytes) { | |
| os->mmap_transfer[PLAY].bytes -= size; | |
| os->mmap_transfer[PLAY].pos += size; | |
| fill_mmap_buffer(os); | |
| } else { | |
| sem_post(&os->mmap_transfer[PLAY].sem); | |
| } | |
| } | |
| static void store_mmap_buffer(struct ossp_stream *os) | |
| { | |
| int rc; | |
| struct fuse_chan *ch = fuse_session_next_chan(os->se, NULL); | |
| unsigned mapid = dsp_dir_to_mapid(os, REC); | |
| size_t pos = os->mmap_transfer[REC].pos; | |
| size_t bytes = os->mmap_transfer[REC].bytes; | |
| struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(bytes); | |
| dbg1_os(os, "store mmap buffer pos=%zu bytes=%zu", pos, bytes); | |
| bufv.buf[0].mem = os->mmap_addr[REC] + pos; | |
| rc = fuse_lowlevel_notify_store(ch, mapid, pos, &bufv, 0); | |
| if (rc < 0) | |
| warn_ose(os, rc, "store_mmap_buffer: failure"); | |
| sem_post(&os->mmap_transfer[REC].sem); | |
| } | |
| #endif | |
| /*************************************************************************** | |
| * Notify poller | |
| */ | |
| static void *notify_poller(void *arg) | |
| { | |
| struct epoll_event events[1024]; | |
| int i, nfds; | |
| repeat: | |
| nfds = epoll_wait(notify_epfd, events, ARRAY_SIZE(events), -1); | |
| for (i = 0; i < nfds; i++) { | |
| int do_notify = 0; | |
| struct ossp_stream *os; | |
| struct ossp_notify notify; | |
| ssize_t ret; | |
| os = find_os_by_notify_rx(events[i].data.fd); | |
| if (!os) { | |
| err("can't find stream for notify_rx fd %d", | |
| events[i].data.fd); | |
| epoll_ctl(notify_epfd, EPOLL_CTL_DEL, events[i].data.fd, | |
| NULL); | |
| /* we don't know what's going on, don't close the fd */ | |
| continue; | |
| } | |
| while ((ret = read(os->notify_rx, | |
| ¬ify, sizeof(notify))) > 0) { | |
| if (os->dead) | |
| continue; | |
| if (ret != sizeof(notify)) { | |
| warn_os(os, "short read on notify_rx (%zu, " | |
| "expected %zu), killing the stream", | |
| ret, sizeof(notify)); | |
| os->dead = 1; | |
| break; | |
| } | |
| if (notify.magic != OSSP_NOTIFY_MAGIC) { | |
| warn_os(os, "invalid magic on notification, " | |
| "killing the stream"); | |
| os->dead = 1; | |
| break; | |
| } | |
| if (notify.opcode >= OSSP_NR_NOTIFY_OPCODES) | |
| goto unknown; | |
| dbg1_os(os, "NOTIFY %s", ossp_notify_str[notify.opcode]); | |
| switch (notify.opcode) { | |
| case OSSP_NOTIFY_POLL: | |
| do_notify = 1; | |
| break; | |
| case OSSP_NOTIFY_OBITUARY: | |
| os->dead = 1; | |
| break; | |
| case OSSP_NOTIFY_VOLCHG: | |
| pthread_mutex_lock(&mixer_mutex); | |
| os->mixer->modify_counter++; | |
| pthread_mutex_unlock(&mixer_mutex); | |
| break; | |
| #ifdef OSSP_MMAP | |
| case OSSP_NOTIFY_FILL: | |
| fill_mmap_buffer(os); | |
| break; | |
| case OSSP_NOTIFY_STORE: | |
| store_mmap_buffer(os); | |
| break; | |
| #endif | |
| default: | |
| unknown: | |
| warn_os(os, "unknown notification %d", | |
| notify.opcode); | |
| } | |
| } | |
| if (ret == 0) | |
| os->dead = 1; | |
| else if (ret < 0 && errno != EAGAIN) { | |
| warn_ose(os, -errno, "read fail on notify fd"); | |
| os->dead = 1; | |
| } | |
| if (!do_notify && !os->dead) | |
| continue; | |
| pthread_mutex_lock(&mutex); | |
| if (os->ph) { | |
| fuse_lowlevel_notify_poll(os->ph); | |
| fuse_pollhandle_destroy(os->ph); | |
| os->ph = NULL; | |
| } | |
| if (os->dead) { | |
| dbg0_os(os, "removing %d from notify poll list", | |
| os->notify_rx); | |
| epoll_ctl(notify_epfd, EPOLL_CTL_DEL, os->notify_rx, | |
| NULL); | |
| close(os->notify_rx); | |
| os->notify_rx = -1; | |
| pthread_cond_broadcast(¬ify_poller_kill_wait); | |
| } | |
| pthread_mutex_unlock(&mutex); | |
| } | |
| goto repeat; | |
| /* not reachable */ | |
| return NULL; | |
| } | |
| /*************************************************************************** | |
| * Slave corpse reaper | |
| */ | |
| static void *slave_reaper(void *arg) | |
| { | |
| struct ossp_stream *os; | |
| int status; | |
| pid_t pid; | |
| pthread_mutex_lock(&mutex); | |
| repeat: | |
| while (list_empty(&slave_corpse_list)) | |
| pthread_cond_wait(&slave_reaper_wait, &mutex); | |
| os = list_first_entry(&slave_corpse_list, struct ossp_stream, link); | |
| list_del_init(&os->link); | |
| pthread_mutex_unlock(&mutex); | |
| do { | |
| pid = waitpid(os->slave_pid, &status, 0); | |
| } while (pid < 0 && errno == EINTR); | |
| if (pid < 0) { | |
| if (errno == ECHILD) | |
| warn_ose(os, -errno, "slave %d already gone?", | |
| os->slave_pid); | |
| else | |
| fatal_e(-errno, "waitpid(%d) failed", os->slave_pid); | |
| } | |
| pthread_mutex_lock(&mutex); | |
| dbg1_os(os, "slave %d reaped", os->slave_pid); | |
| __clear_bit(os->id, os_id_bitmap); | |
| if (os->mmap_size) { | |
| sem_destroy(&os->mmap_transfer[PLAY].sem); | |
| sem_destroy(&os->mmap_transfer[REC].sem); | |
| munmap(os->mmap, os->mmap_size + 2 * sizeof(struct ossp_transfer)); | |
| close(os->mmap_fd); | |
| } | |
| free(os); | |
| goto repeat; | |
| /* not reachable */ | |
| return NULL; | |
| } | |
| /*************************************************************************** | |
| * Stuff to bind and start everything | |
| */ | |
| static void ossp_daemonize(void) | |
| { | |
| int fd, pfd[2]; | |
| pid_t pid; | |
| ssize_t ret; | |
| int err; | |
| fd = open("/dev/null", O_RDWR); | |
| if (fd >= 0) { | |
| dup2(fd, 0); | |
| dup2(fd, 1); | |
| dup2(fd, 2); | |
| if (fd > 2) | |
| close(fd); | |
| } | |
| if (pipe(pfd)) | |
| fatal_e(-errno, "failed to create pipe for init wait"); | |
| if (fcntl(pfd[0], F_SETFD, FD_CLOEXEC) < 0 || | |
| fcntl(pfd[1], F_SETFD, FD_CLOEXEC) < 0) | |
| fatal_e(-errno, "failed to set CLOEXEC on init wait pipe"); | |
| pid = fork(); | |
| if (pid < 0) | |
| fatal_e(-errno, "failed to fork for daemon"); | |
| if (pid == 0) { | |
| close(pfd[0]); | |
| init_wait_fd = pfd[1]; | |
| /* be evil, my child */ | |
| chdir("/"); | |
| setsid(); | |
| return; | |
| } | |
| /* wait for init completion and pass over success indication */ | |
| close(pfd[1]); | |
| do { | |
| ret = read(pfd[0], &err, sizeof(err)); | |
| } while (ret < 0 && errno == EINTR); | |
| if (ret == sizeof(err) && err == 0) | |
| exit(0); | |
| fatal("daemon init failed ret=%zd err=%d", ret, err); | |
| exit(1); | |
| } | |
| static void ossp_init_done(void *userdata) | |
| { | |
| /* init complete, notify parent if it's waiting */ | |
| if (init_wait_fd >= 0) { | |
| ssize_t ret; | |
| int err = 0; | |
| ret = write(init_wait_fd, &err, sizeof(err)); | |
| if (ret != sizeof(err)) | |
| fatal_e(-errno, "failed to notify init completion, " | |
| "ret=%zd", ret); | |
| close(init_wait_fd); | |
| init_wait_fd = -1; | |
| } | |
| } | |
| static const struct cuse_lowlevel_ops mixer_ops = { | |
| .open = mixer_open, | |
| .release = mixer_release, | |
| .ioctl = mixer_ioctl, | |
| }; | |
| static const struct cuse_lowlevel_ops dsp_ops = { | |
| .init_done = ossp_init_done, | |
| .open = dsp_open, | |
| .release = dsp_release, | |
| .read = dsp_read, | |
| .write = dsp_write, | |
| .poll = dsp_poll, | |
| .ioctl = dsp_ioctl, | |
| #ifdef OSSP_MMAP | |
| .mmap = dsp_mmap, | |
| .munmap = dsp_munmap, | |
| .retrieve_reply = dsp_retrieve_reply, | |
| #endif | |
| }; | |
| static const struct cuse_lowlevel_ops adsp_ops = { | |
| .open = adsp_open, | |
| .release = dsp_release, | |
| .read = dsp_read, | |
| .write = dsp_write, | |
| .poll = dsp_poll, | |
| .ioctl = dsp_ioctl, | |
| #ifdef OSSP_MMAP | |
| .mmap = dsp_mmap, | |
| .munmap = dsp_munmap, | |
| .retrieve_reply = dsp_retrieve_reply, | |
| #endif | |
| }; | |
| static const char *usage = | |
| "usage: osspd [options]\n" | |
| "\n" | |
| "options:\n" | |
| " --help print this help message\n" | |
| " --dsp=NAME DSP device name (default dsp)\n" | |
| " --dsp-maj=MAJ DSP device major number (default 14)\n" | |
| " --dsp-min=MIN DSP device minor number (default 3)\n" | |
| " --adsp=NAME Aux DSP device name (default adsp, blank to disable)\n" | |
| " --adsp-maj=MAJ Aux DSP device major number (default 14)\n" | |
| " --adsp-min=MIN Aux DSP device minor number (default 12)\n" | |
| " --mixer=NAME mixer device name (default mixer, blank to disable)\n" | |
| " --mixer-maj=MAJ mixer device major number (default 14)\n" | |
| " --mixer-min=MIN mixer device minor number (default 0)\n" | |
| " --max=MAX maximum number of open streams (default 256)\n" | |
| " --umax=MAX maximum number of open streams per UID (default --max)\n" | |
| " --exit-on-idle exit if idle\n" | |
| " --dsp-slave=PATH DSP slave (default ossp-padsp in the same dir)\n" | |
| " --log=LEVEL log level (0..6)\n" | |
| " --timestamp timestamp log messages\n" | |
| " -v increase verbosity, can be specified multiple times\n" | |
| " -f Run in foreground (don't daemonize)\n" | |
| "\n"; | |
| struct ossp_param { | |
| char *dsp_name; | |
| unsigned dsp_major; | |
| unsigned dsp_minor; | |
| char *adsp_name; | |
| unsigned adsp_major; | |
| unsigned adsp_minor; | |
| char *mixer_name; | |
| unsigned mixer_major; | |
| unsigned mixer_minor; | |
| unsigned max_streams; | |
| unsigned umax_streams; | |
| char *dsp_slave_path; | |
| unsigned log_level; | |
| int exit_on_idle; | |
| int timestamp; | |
| int fg; | |
| int help; | |
| }; | |
| #define OSSP_OPT(t, p) { t, offsetof(struct ossp_param, p), 1 } | |
| static const struct fuse_opt ossp_opts[] = { | |
| OSSP_OPT("--dsp=%s", dsp_name), | |
| OSSP_OPT("--dsp-maj=%u", dsp_major), | |
| OSSP_OPT("--dsp-min=%u", dsp_minor), | |
| OSSP_OPT("--adsp=%s", adsp_name), | |
| OSSP_OPT("--adsp-maj=%u", adsp_major), | |
| OSSP_OPT("--adsp-min=%u", adsp_minor), | |
| OSSP_OPT("--mixer=%s", mixer_name), | |
| OSSP_OPT("--mixer-maj=%u", mixer_major), | |
| OSSP_OPT("--mixer-min=%u", mixer_minor), | |
| OSSP_OPT("--max=%u", max_streams), | |
| OSSP_OPT("--umax=%u", umax_streams), | |
| OSSP_OPT("--exit-on-idle", exit_on_idle), | |
| OSSP_OPT("--dsp-slave=%s", dsp_slave_path), | |
| OSSP_OPT("--timestamp", timestamp), | |
| OSSP_OPT("--log=%u", log_level), | |
| OSSP_OPT("-f", fg), | |
| FUSE_OPT_KEY("-h", 0), | |
| FUSE_OPT_KEY("--help", 0), | |
| FUSE_OPT_KEY("-v", 1), | |
| FUSE_OPT_END | |
| }; | |
| static struct fuse_session *setup_ossp_cuse(const struct cuse_lowlevel_ops *ops, | |
| const char *name, int major, | |
| int minor, int argc, char **argv) | |
| { | |
| char name_buf[128]; | |
| const char *bufp = name_buf; | |
| struct cuse_info ci = { .dev_major = major, .dev_minor = minor, | |
| .dev_info_argc = 1, .dev_info_argv = &bufp, | |
| .flags = CUSE_UNRESTRICTED_IOCTL }; | |
| struct fuse_session *se; | |
| int fd; | |
| snprintf(name_buf, sizeof(name_buf), "DEVNAME=%s", name); | |
| se = cuse_lowlevel_setup(argc, argv, &ci, ops, NULL, NULL); | |
| if (!se) { | |
| err("failed to setup %s CUSE", name); | |
| return NULL; | |
| } | |
| fd = fuse_chan_fd(fuse_session_next_chan(se, NULL)); | |
| if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { | |
| err_e(-errno, "failed to set CLOEXEC on %s CUSE fd", name); | |
| cuse_lowlevel_teardown(se); | |
| return NULL; | |
| } | |
| return se; | |
| } | |
| static void *cuse_worker(void *arg) | |
| { | |
| struct fuse_session *se = arg; | |
| int rc; | |
| rc = fuse_session_loop_mt(se); | |
| cuse_lowlevel_teardown(se); | |
| return (void *)(unsigned long)rc; | |
| } | |
| static int process_arg(void *data, const char *arg, int key, | |
| struct fuse_args *outargs) | |
| { | |
| struct ossp_param *param = data; | |
| switch (key) { | |
| case 0: | |
| fprintf(stderr, usage); | |
| param->help = 1; | |
| return 0; | |
| case 1: | |
| param->log_level++; | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| static struct ossp_param param = { | |
| .dsp_name = DFL_DSP_NAME, | |
| .dsp_major = DFL_DSP_MAJOR, .dsp_minor = DFL_DSP_MINOR, | |
| .adsp_name = DFL_ADSP_NAME, | |
| .adsp_major = DFL_ADSP_MAJOR, .adsp_minor = DFL_ADSP_MINOR, | |
| .mixer_name = DFL_MIXER_NAME, | |
| .mixer_major = DFL_MIXER_MAJOR, .mixer_minor = DFL_MIXER_MINOR, | |
| .max_streams = DFL_MAX_STREAMS, | |
| }; | |
| struct fuse_args args = FUSE_ARGS_INIT(argc, argv); | |
| char path_buf[PATH_MAX], *dir; | |
| char adsp_buf[64] = "", mixer_buf[64] = ""; | |
| struct sigaction sa; | |
| struct stat stat_buf; | |
| ssize_t ret; | |
| unsigned u; | |
| snprintf(ossp_log_name, sizeof(ossp_log_name), "osspd"); | |
| param.log_level = ossp_log_level; | |
| if (fuse_opt_parse(&args, ¶m, ossp_opts, process_arg)) | |
| fatal("failed to parse arguments"); | |
| if (param.help) | |
| return 0; | |
| max_streams = param.max_streams; | |
| hashtbl_size = max_streams / 2 + 13; | |
| umax_streams = max_streams; | |
| if (param.umax_streams) | |
| umax_streams = param.umax_streams; | |
| if (param.log_level > OSSP_LOG_MAX) | |
| param.log_level = OSSP_LOG_MAX; | |
| if (!param.fg) | |
| param.log_level = -param.log_level; | |
| ossp_log_level = param.log_level; | |
| ossp_log_timestamp = param.timestamp; | |
| if (!param.fg) | |
| ossp_daemonize(); | |
| /* daemonization already handled, prevent forking inside FUSE */ | |
| fuse_opt_add_arg(&args, "-f"); | |
| info("OSS Proxy v%s (C) 2008-2010 by Tejun Heo <teheo@suse.de>", | |
| OSSP_VERSION); | |
| /* ignore stupid SIGPIPEs */ | |
| memset(&sa, 0, sizeof(sa)); | |
| sa.sa_handler = SIG_IGN; | |
| if (sigaction(SIGPIPE, &sa, NULL)) | |
| fatal_e(-errno, "failed to ignore SIGPIPE"); | |
| /* determine slave path and check for availability */ | |
| ret = readlink("/proc/self/exe", path_buf, PATH_MAX - 1); | |
| if (ret < 0) | |
| fatal_e(-errno, "failed to determine executable path"); | |
| path_buf[ret] = '\0'; | |
| dir = dirname(path_buf); | |
| if (param.dsp_slave_path) { | |
| strncpy(dsp_slave_path, param.dsp_slave_path, PATH_MAX - 1); | |
| dsp_slave_path[PATH_MAX - 1] = '\0'; | |
| } else { | |
| ret = snprintf(dsp_slave_path, PATH_MAX, "%s/%s", | |
| dir, "ossp-padsp"); | |
| if (ret >= PATH_MAX) | |
| fatal("dsp slave pathname too long"); | |
| } | |
| if (stat(dsp_slave_path, &stat_buf)) | |
| fatal_e(-errno, "failed to stat %s", dsp_slave_path); | |
| if (!S_ISREG(stat_buf.st_mode) || !(stat_buf.st_mode & 0444)) | |
| fatal("%s is not executable", dsp_slave_path); | |
| /* allocate tables */ | |
| os_id_bitmap = calloc(BITS_TO_LONGS(max_streams), sizeof(long)); | |
| mixer_tbl = calloc(hashtbl_size, sizeof(mixer_tbl[0])); | |
| os_tbl = calloc(hashtbl_size, sizeof(os_tbl[0])); | |
| os_pgrp_tbl = calloc(hashtbl_size, sizeof(os_pgrp_tbl[0])); | |
| os_notify_tbl = calloc(hashtbl_size, sizeof(os_notify_tbl[0])); | |
| if (!os_id_bitmap || !mixer_tbl || !os_tbl || !os_pgrp_tbl || | |
| !os_notify_tbl) | |
| fatal("failed to allocate stream hash tables"); | |
| for (u = 0; u < hashtbl_size; u++) { | |
| INIT_LIST_HEAD(&mixer_tbl[u]); | |
| INIT_LIST_HEAD(&os_tbl[u]); | |
| INIT_LIST_HEAD(&os_pgrp_tbl[u]); | |
| INIT_LIST_HEAD(&os_notify_tbl[u]); | |
| } | |
| __set_bit(0, os_id_bitmap); /* don't use id 0 */ | |
| /* create mixer delayed reference worker */ | |
| ret = -pthread_create(&mixer_delayed_put_thread, NULL, | |
| mixer_delayed_put_worker, NULL); | |
| if (ret) | |
| fatal_e(ret, "failed to create mixer delayed put worker"); | |
| /* if exit_on_idle, touch mixer for pgrp0 */ | |
| exit_on_idle = param.exit_on_idle; | |
| if (exit_on_idle) { | |
| struct ossp_mixer *mixer; | |
| mixer = get_mixer(0); | |
| if (!mixer) | |
| fatal("failed to touch idle mixer"); | |
| put_mixer(mixer); | |
| } | |
| /* create notify epoll and kick off watcher thread */ | |
| notify_epfd = epoll_create(max_streams); | |
| if (notify_epfd < 0) | |
| fatal_e(-errno, "failed to create notify epoll"); | |
| if (fcntl(notify_epfd, F_SETFD, FD_CLOEXEC) < 0) | |
| fatal_e(-errno, "failed to set CLOEXEC on notify epfd"); | |
| ret = -pthread_create(¬ify_poller_thread, NULL, notify_poller, NULL); | |
| if (ret) | |
| fatal_e(ret, "failed to create notify poller thread"); | |
| /* create reaper for slave corpses */ | |
| ret = -pthread_create(&slave_reaper_thread, NULL, slave_reaper, NULL); | |
| if (ret) | |
| fatal_e(ret, "failed to create slave reaper thread"); | |
| /* we're set, let's setup fuse structures */ | |
| if (strlen(param.mixer_name)) | |
| mixer_se = setup_ossp_cuse(&mixer_ops, param.mixer_name, | |
| param.mixer_major, param.mixer_minor, | |
| args.argc, args.argv); | |
| if (strlen(param.adsp_name)) | |
| adsp_se = setup_ossp_cuse(&adsp_ops, param.adsp_name, | |
| param.adsp_major, param.adsp_minor, | |
| args.argc, args.argv); | |
| dsp_se = setup_ossp_cuse(&dsp_ops, param.dsp_name, | |
| param.dsp_major, param.dsp_minor, | |
| args.argc, args.argv); | |
| if (!dsp_se) | |
| fatal("can't create dsp, giving up"); | |
| if (mixer_se) | |
| snprintf(mixer_buf, sizeof(mixer_buf), ", %s (%d:%d)", | |
| param.mixer_name, param.mixer_major, param.mixer_minor); | |
| if (adsp_se) | |
| snprintf(adsp_buf, sizeof(adsp_buf), ", %s (%d:%d)", | |
| param.adsp_name, param.adsp_major, param.adsp_minor); | |
| info("Creating %s (%d:%d)%s%s", param.dsp_name, param.dsp_major, | |
| param.dsp_minor, adsp_buf, mixer_buf); | |
| /* start threads for mixer and adsp */ | |
| if (mixer_se) { | |
| ret = -pthread_create(&cuse_mixer_thread, NULL, | |
| cuse_worker, mixer_se); | |
| if (ret) | |
| err_e(ret, "failed to create mixer worker"); | |
| } | |
| if (adsp_se) { | |
| ret = -pthread_create(&cuse_adsp_thread, NULL, | |
| cuse_worker, adsp_se); | |
| if (ret) | |
| err_e(ret, "failed to create adsp worker"); | |
| } | |
| /* run CUSE for /dev/dsp in the main thread */ | |
| ret = (ssize_t)cuse_worker(dsp_se); | |
| if (ret < 0) | |
| fatal("dsp worker failed"); | |
| return 0; | |
| } |