summaryrefslogtreecommitdiff
path: root/cras/src/server/cras_server.c
diff options
context:
space:
mode:
Diffstat (limited to 'cras/src/server/cras_server.c')
-rw-r--r--cras/src/server/cras_server.c720
1 files changed, 0 insertions, 720 deletions
diff --git a/cras/src/server/cras_server.c b/cras/src/server/cras_server.c
deleted file mode 100644
index 5f2ce632..00000000
--- a/cras/src/server/cras_server.c
+++ /dev/null
@@ -1,720 +0,0 @@
-/* Copyright (c) 2012 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.
- */
-
-#define _GNU_SOURCE /* Needed for Linux socket credential passing. */
-
-#ifdef CRAS_DBUS
-#include <dbus/dbus.h>
-#endif
-#include <errno.h>
-#include <poll.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/param.h>
-#include <sys/select.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/un.h>
-#include <syslog.h>
-#include <unistd.h>
-
-#ifdef CRAS_DBUS
-#include "cras_a2dp_endpoint.h"
-#include "cras_bt_manager.h"
-#include "cras_bt_device.h"
-#include "cras_bt_player.h"
-#include "cras_dbus.h"
-#include "cras_dbus_control.h"
-#include "cras_hfp_ag_profile.h"
-#include "cras_telephony.h"
-#endif
-#include "cras_alert.h"
-#include "cras_audio_thread_monitor.h"
-#include "cras_config.h"
-#include "cras_device_monitor.h"
-#include "cras_hotword_handler.h"
-#include "cras_iodev_list.h"
-#include "cras_main_message.h"
-#include "cras_messages.h"
-#include "cras_metrics.h"
-#include "cras_non_empty_audio_handler.h"
-#include "cras_observer.h"
-#include "cras_rclient.h"
-#include "cras_server.h"
-#include "cras_server_metrics.h"
-#include "cras_system_state.h"
-#include "cras_tm.h"
-#include "cras_types.h"
-#include "cras_udev.h"
-#include "cras_util.h"
-#include "cras_mix.h"
-#include "utlist.h"
-
-/* Store a list of clients that are attached to the server.
- * Members:
- * id - Unique identifier for this client.
- * fd - socket file descriptor used to communicate with client.
- * ucred - Process, user, and group ID of the client.
- * client - rclient to handle messages from this client.
- * pollfd - Pointer to struct pollfd for this callback.
- */
-struct attached_client {
- size_t id;
- int fd;
- struct ucred ucred;
- struct cras_rclient *client;
- struct pollfd *pollfd;
- struct attached_client *next, *prev;
-};
-
-/* Stores file descriptors to callback mappings for clients. Callback/fd/data
- * args are registered by clients. When fd is ready, the callback will be
- * called on the main server thread and the callback data will be passed back to
- * it. This allows the use of the main server loop instead of spawning a thread
- * to watch file descriptors. The client can then read or write the fd.
- * Members:
- * fd - The file descriptor passed to select.
- * callback - The funciton to call when fd is ready.
- * callback_data - Pointer passed to the callback.
- * pollfd - Pointer to struct pollfd for this callback.
- * events - The events to poll for.
- */
-struct client_callback {
- int select_fd;
- void (*callback)(void *data, int revents);
- void *callback_data;
- struct pollfd *pollfd;
- int deleted;
- int events;
- struct client_callback *prev, *next;
-};
-
-/* Stores callback function and argument data to be executed later. */
-struct system_task {
- void (*callback)(void *);
- void *callback_data;
- struct system_task *next, *prev;
-};
-
-/* A structure wraps data related to server socket. */
-struct server_socket {
- struct sockaddr_un addr;
- int fd;
- enum CRAS_CONNECTION_TYPE type;
-};
-
-/* Local server data. */
-struct server_data {
- struct attached_client *clients_head;
- size_t num_clients;
- struct client_callback *client_callbacks;
- struct system_task *system_tasks;
- size_t num_client_callbacks;
- size_t next_client_id;
- struct server_socket server_sockets[CRAS_NUM_CONN_TYPE];
-} server_instance;
-
-/* Cleanup a given server_socket */
-static void server_socket_cleanup(struct server_socket *socket)
-{
- if (socket && socket->fd >= 0) {
- close(socket->fd);
- socket->fd = -1;
- unlink(socket->addr.sun_path);
- }
-}
-
-/* Remove a client from the list and destroy it. Calling rclient_destroy will
- * also free all the streams owned by the client */
-static void remove_client(struct attached_client *client)
-{
- close(client->fd);
- DL_DELETE(server_instance.clients_head, client);
- server_instance.num_clients--;
- cras_rclient_destroy(client->client);
- free(client);
-}
-
-/* This is called when "select" indicates that the client has written data to
- * the socket. Read out one message and pass it to the client message handler.
- */
-static void handle_message_from_client(struct attached_client *client)
-{
- uint8_t buf[CRAS_SERV_MAX_MSG_SIZE];
- int nread;
- unsigned int num_fds = 2;
- int fds[num_fds];
-
- nread = cras_recv_with_fds(client->fd, buf, sizeof(buf), fds, &num_fds);
- if (nread < 0)
- goto read_error;
- if (cras_rclient_buffer_from_client(client->client, buf, nread, fds,
- num_fds) < 0)
- goto read_error;
- return;
-
-read_error:
- for (int i = 0; i < num_fds; i++)
- if (fds[i] >= 0)
- close(fds[i]);
- switch (nread) {
- case 0:
- break;
- default:
- syslog(LOG_DEBUG, "read err [%d] '%s', removing client %zu",
- -nread, strerror(-nread), client->id);
- break;
- }
- remove_client(client);
-}
-
-/* Discovers and fills in info about the client that can be obtained from the
- * socket. The pid of the attaching client identifies it in logs. */
-static void fill_client_info(struct attached_client *client)
-{
- socklen_t ucred_length = sizeof(client->ucred);
-
- if (getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &client->ucred,
- &ucred_length))
- syslog(LOG_INFO, "Failed to get client socket info\n");
-}
-
-/* Fills the server_state with the current list of attached clients. */
-static void send_client_list_to_clients(struct server_data *serv)
-{
- struct attached_client *c;
- struct cras_attached_client_info *info;
- struct cras_server_state *state;
- unsigned i;
-
- state = cras_system_state_update_begin();
- if (!state)
- return;
-
- state->num_attached_clients =
- MIN(CRAS_MAX_ATTACHED_CLIENTS, serv->num_clients);
-
- info = state->client_info;
- i = 0;
- DL_FOREACH (serv->clients_head, c) {
- info->id = c->id;
- info->pid = c->ucred.pid;
- info->uid = c->ucred.uid;
- info->gid = c->ucred.gid;
- info++;
- if (++i == CRAS_MAX_ATTACHED_CLIENTS)
- break;
- }
-
- cras_system_state_update_complete();
-}
-
-/* Handles requests from a client to attach to the server. Create a local
- * structure to track the client, assign it a unique id and let it attach */
-static void handle_new_connection(struct server_socket *server_socket)
-{
- int connection_fd;
- struct attached_client *poll_client;
- socklen_t address_length;
-
- poll_client = malloc(sizeof(struct attached_client));
- if (poll_client == NULL) {
- syslog(LOG_ERR, "Allocating poll_client");
- return;
- }
-
- memset(&address_length, 0, sizeof(address_length));
- connection_fd = accept(server_socket->fd,
- (struct sockaddr *)&server_socket->addr,
- &address_length);
- if (connection_fd < 0) {
- syslog(LOG_ERR, "connecting");
- free(poll_client);
- return;
- }
-
- /* find next available client id */
- while (1) {
- struct attached_client *out;
- DL_SEARCH_SCALAR(server_instance.clients_head, out, id,
- server_instance.next_client_id);
- poll_client->id = server_instance.next_client_id;
- server_instance.next_client_id++;
- if (out == NULL)
- break;
- }
-
- /* When full, getting an error is preferable to blocking. */
- cras_make_fd_nonblocking(connection_fd);
-
- poll_client->fd = connection_fd;
- poll_client->next = NULL;
- poll_client->pollfd = NULL;
- fill_client_info(poll_client);
-
- poll_client->client = cras_rclient_create(
- connection_fd, poll_client->id, server_socket->type);
- if (poll_client->client == NULL) {
- syslog(LOG_ERR, "failed to create client");
- goto error;
- }
-
- DL_APPEND(server_instance.clients_head, poll_client);
- server_instance.num_clients++;
- /* Send a current list of available inputs and outputs. */
- cras_iodev_list_update_device_list();
- send_client_list_to_clients(&server_instance);
- return;
-error:
- close(connection_fd);
- free(poll_client);
- return;
-}
-
-/* Add a file descriptor to be passed to select in the main loop. This is
- * registered with system state so that it is called when any client asks to
- * have a callback triggered based on an fd being readable. */
-static int add_select_fd(int fd, void (*cb)(void *data, int events),
- void *callback_data, int events, void *server_data)
-{
- struct client_callback *new_cb;
- struct client_callback *client_cb;
- struct server_data *serv;
-
- serv = (struct server_data *)server_data;
- if (serv == NULL)
- return -EINVAL;
-
- /* Check if fd already exists. */
- DL_FOREACH (serv->client_callbacks, client_cb)
- if (client_cb->select_fd == fd && !client_cb->deleted)
- return -EEXIST;
-
- new_cb = (struct client_callback *)calloc(1, sizeof(*new_cb));
- if (new_cb == NULL)
- return -ENOMEM;
-
- new_cb->select_fd = fd;
- new_cb->callback = cb;
- new_cb->callback_data = callback_data;
- new_cb->deleted = 0;
- new_cb->events = events;
- new_cb->pollfd = NULL;
-
- DL_APPEND(serv->client_callbacks, new_cb);
- server_instance.num_client_callbacks++;
- return 0;
-}
-
-/* Removes a file descriptor to be passed to select in the main loop. This is
- * registered with system state so that it is called when any client asks to
- * remove a callback added with add_select_fd. */
-static void rm_select_fd(int fd, void *server_data)
-{
- struct server_data *serv;
- struct client_callback *client_cb;
-
- serv = (struct server_data *)server_data;
- if (serv == NULL)
- return;
-
- DL_FOREACH (serv->client_callbacks, client_cb)
- if (client_cb->select_fd == fd)
- client_cb->deleted = 1;
-}
-
-/* Creates a new task entry and append to system_tasks list, which will be
- * executed in main loop later without wait time.
- */
-static int add_task(void (*cb)(void *data), void *callback_data,
- void *server_data)
-{
- struct server_data *serv;
- struct system_task *new_task;
-
- serv = (struct server_data *)server_data;
- if (serv == NULL)
- return -EINVAL;
-
- new_task = (struct system_task *)calloc(1, sizeof(*new_task));
- if (new_task == NULL)
- return -ENOMEM;
-
- new_task->callback = cb;
- new_task->callback_data = callback_data;
-
- DL_APPEND(serv->system_tasks, new_task);
- return 0;
-}
-
-/* Cleans up the file descriptor list removing items deleted during the main
- * loop iteration. */
-static void cleanup_select_fds(void *server_data)
-{
- struct server_data *serv;
- struct client_callback *client_cb;
-
- serv = (struct server_data *)server_data;
- if (serv == NULL)
- return;
-
- DL_FOREACH (serv->client_callbacks, client_cb)
- if (client_cb->deleted) {
- DL_DELETE(serv->client_callbacks, client_cb);
- server_instance.num_client_callbacks--;
- free(client_cb);
- }
-}
-
-/* Checks that at least two outputs are present (one will be the "empty"
- * default device. */
-void check_output_exists(struct cras_timer *t, void *data)
-{
- if (cras_iodev_list_get_outputs(NULL) < 2)
- cras_metrics_log_event(kNoCodecsFoundMetric);
-}
-
-#if defined(__amd64__)
-/* CPU detection - probaby best to move this elsewhere */
-static void cpuid(unsigned int *eax, unsigned int *ebx, unsigned int *ecx,
- unsigned int *edx, unsigned int op)
-{
- // clang-format off
- __asm__ __volatile__ (
- "cpuid"
- : "=a" (*eax),
- "=b" (*ebx),
- "=c" (*ecx),
- "=d" (*edx)
- : "a" (op), "c" (0)
- );
- // clang-format on
-}
-
-static unsigned int cpu_x86_flags(void)
-{
- unsigned int eax, ebx, ecx, edx, id;
- unsigned int cpu_flags = 0;
-
- cpuid(&id, &ebx, &ecx, &edx, 0);
-
- if (id >= 1) {
- cpuid(&eax, &ebx, &ecx, &edx, 1);
-
- if (ecx & (1 << 20))
- cpu_flags |= CPU_X86_SSE4_2;
-
- if (ecx & (1 << 28))
- cpu_flags |= CPU_X86_AVX;
-
- if (ecx & (1 << 12))
- cpu_flags |= CPU_X86_FMA;
- }
-
- if (id >= 7) {
- cpuid(&eax, &ebx, &ecx, &edx, 7);
-
- if (ebx & (1 << 5))
- cpu_flags |= CPU_X86_AVX2;
- }
-
- return cpu_flags;
-}
-#endif
-
-int cpu_get_flags(void)
-{
-#if defined(__amd64__)
- return cpu_x86_flags();
-#endif
- return 0;
-}
-
-/*
- * Exported Interface.
- */
-
-int cras_server_init()
-{
- /* Log to syslog. */
- openlog("cras_server", LOG_PID, LOG_USER);
-
- server_instance.next_client_id = RESERVED_CLIENT_IDS;
-
- /* Initialize global observer. */
- cras_observer_server_init();
-
- /* init mixer with CPU capabilities */
- cras_mix_init(cpu_get_flags());
-
- /* Allow clients to register callbacks for file descriptors.
- * add_select_fd and rm_select_fd will add and remove file descriptors
- * from the list that are passed to select in the main loop below. */
- cras_system_set_select_handler(add_select_fd, rm_select_fd,
- &server_instance);
- cras_system_set_add_task_handler(add_task, &server_instance);
- cras_main_message_init();
-
- /* Initializes all server_sockets */
- for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE; conn_type++) {
- server_instance.server_sockets[conn_type].fd = -1;
- }
-
- return 0;
-}
-
-/*
- * Creates a server socket with given connection type and listens on it.
- * The socket_file will be created under cras_config_get_system_socket_file_dir
- * with permission=0770. The socket_fd will be listened with parameter
- * backlog=5.
- *
- * Returns 0 on success and leaves the created fd and the address information
- * in server_socket.
- * When error occurs, the created fd will be closed and the file path will be
- * unlinked and returns negative error code.
- */
-static int create_and_listen_server_socket(enum CRAS_CONNECTION_TYPE conn_type,
- struct server_socket *server_socket)
-{
- int socket_fd = -1;
- int rc = 0;
- struct sockaddr_un *addr = &server_socket->addr;
-
- socket_fd = socket(PF_UNIX, SOCK_SEQPACKET, 0);
- if (socket_fd < 0) {
- syslog(LOG_ERR, "Main server socket failed.");
- rc = socket_fd;
- goto error;
- }
-
- memset(addr, 0, sizeof(*addr));
- addr->sun_family = AF_UNIX;
- rc = cras_fill_socket_path(conn_type, addr->sun_path);
- if (rc < 0)
- goto error;
- unlink(addr->sun_path);
-
- /* Linux quirk: calling fchmod before bind, sets the permissions of the
- * file created by bind, leaving no window for it to be modified. Start
- * with very restricted permissions. */
- rc = fchmod(socket_fd, 0700);
- if (rc < 0)
- goto error;
-
- rc = bind(socket_fd, (struct sockaddr *)addr,
- sizeof(struct sockaddr_un));
- if (rc < 0) {
- syslog(LOG_ERR, "Bind to server socket failed.");
- rc = -errno;
- goto error;
- }
-
- /* Let other members in our group play audio through this socket. */
- rc = chmod(addr->sun_path, 0770);
- if (rc < 0)
- goto error;
-
- if (listen(socket_fd, 5) != 0) {
- syslog(LOG_ERR, "Listen on server socket failed.");
- rc = -errno;
- goto error;
- }
-
- server_socket->fd = socket_fd;
- server_socket->type = conn_type;
- return 0;
-error:
- if (socket_fd >= 0) {
- close(socket_fd);
- unlink(addr->sun_path);
- }
- return rc;
-}
-
-/* Cleans up all server_socket in server_instance */
-static void cleanup_server_sockets()
-{
- for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE; conn_type++) {
- server_socket_cleanup(
- &server_instance.server_sockets[conn_type]);
- }
-}
-
-int cras_server_run(unsigned int profile_disable_mask)
-{
- static const unsigned int OUTPUT_CHECK_MS = 5 * 1000;
-#ifdef CRAS_DBUS
- DBusConnection *dbus_conn;
-#endif
- int rc = 0;
- struct attached_client *elm;
- struct client_callback *client_cb;
- struct system_task *tasks;
- struct system_task *system_task;
- struct cras_tm *tm;
- struct timespec ts, *poll_timeout;
- int timers_active;
- struct pollfd *pollfds;
- unsigned int pollfds_size = 32;
- unsigned int num_pollfds, poll_size_needed;
-
- pollfds = malloc(sizeof(*pollfds) * pollfds_size);
-
- cras_udev_start_sound_subsystem_monitor();
-#ifdef CRAS_DBUS
- cras_bt_device_start_monitor();
-#endif
-
- cras_server_metrics_init();
-
- cras_device_monitor_init();
-
- cras_hotword_handler_init();
-
- cras_non_empty_audio_handler_init();
-
- cras_audio_thread_monitor_init();
-
-#ifdef CRAS_DBUS
- dbus_threads_init_default();
- dbus_conn = cras_dbus_connect_system_bus();
- if (dbus_conn) {
- cras_bt_start(dbus_conn);
- if (!(profile_disable_mask & CRAS_SERVER_PROFILE_MASK_HFP))
- cras_hfp_ag_profile_create(dbus_conn);
- if (!(profile_disable_mask & CRAS_SERVER_PROFILE_MASK_HSP))
- cras_hsp_ag_profile_create(dbus_conn);
- cras_telephony_start(dbus_conn);
- if (!(profile_disable_mask & CRAS_SERVER_PROFILE_MASK_A2DP))
- cras_a2dp_endpoint_create(dbus_conn);
- cras_bt_player_create(dbus_conn);
- cras_dbus_control_start(dbus_conn);
- }
-#endif
-
- for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE; conn_type++) {
- rc = create_and_listen_server_socket(
- conn_type, &server_instance.server_sockets[conn_type]);
- if (rc < 0)
- goto bail;
- }
-
- tm = cras_system_state_get_tm();
- if (!tm) {
- syslog(LOG_ERR, "Getting timer manager.");
- rc = -ENOMEM;
- goto bail;
- }
-
- /* After a delay, make sure there is at least one real output device. */
- cras_tm_create_timer(tm, OUTPUT_CHECK_MS, check_output_exists, 0);
-
- /* Main server loop - client callbacks are run from this context. */
- while (1) {
- poll_size_needed = CRAS_NUM_CONN_TYPE +
- server_instance.num_clients +
- server_instance.num_client_callbacks;
- if (poll_size_needed > pollfds_size) {
- pollfds_size = 2 * poll_size_needed;
- pollfds = realloc(pollfds,
- sizeof(*pollfds) * pollfds_size);
- }
-
- for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE;
- conn_type++) {
- pollfds[conn_type].fd =
- server_instance.server_sockets[conn_type].fd;
- pollfds[conn_type].events = POLLIN;
- }
- num_pollfds = CRAS_NUM_CONN_TYPE;
-
- DL_FOREACH (server_instance.clients_head, elm) {
- pollfds[num_pollfds].fd = elm->fd;
- pollfds[num_pollfds].events = POLLIN;
- elm->pollfd = &pollfds[num_pollfds];
- num_pollfds++;
- }
- DL_FOREACH (server_instance.client_callbacks, client_cb) {
- if (client_cb->deleted)
- continue;
- pollfds[num_pollfds].fd = client_cb->select_fd;
- pollfds[num_pollfds].events = client_cb->events;
- client_cb->pollfd = &pollfds[num_pollfds];
- num_pollfds++;
- }
-
- tasks = server_instance.system_tasks;
- server_instance.system_tasks = NULL;
- DL_FOREACH (tasks, system_task) {
- system_task->callback(system_task->callback_data);
- DL_DELETE(tasks, system_task);
- free(system_task);
- }
-
- timers_active = cras_tm_get_next_timeout(tm, &ts);
-
- /*
- * If new client task has been scheduled, no need to wait
- * for timeout, just do another loop to execute them.
- */
- if (server_instance.system_tasks)
- poll_timeout = NULL;
- else
- poll_timeout = timers_active ? &ts : NULL;
-
- rc = ppoll(pollfds, num_pollfds, poll_timeout, NULL);
- if (rc < 0)
- continue;
-
- cras_tm_call_callbacks(tm);
-
- /* Check for new connections. */
- for (int conn_type = 0; conn_type < CRAS_NUM_CONN_TYPE;
- conn_type++) {
- if (pollfds[conn_type].revents & POLLIN)
- handle_new_connection(
- &server_instance
- .server_sockets[conn_type]);
- }
-
- /* Check if there are messages pending for any clients. */
- DL_FOREACH (server_instance.clients_head, elm)
- if (elm->pollfd && elm->pollfd->revents & POLLIN)
- handle_message_from_client(elm);
- /* Check any client-registered fd/callback pairs. */
- DL_FOREACH (server_instance.client_callbacks, client_cb)
- if (!client_cb->deleted && client_cb->pollfd &&
- (client_cb->pollfd->revents & client_cb->events))
- client_cb->callback(client_cb->callback_data,
- client_cb->pollfd->revents);
-
- cleanup_select_fds(&server_instance);
-
-#ifdef CRAS_DBUS
- if (dbus_conn)
- cras_dbus_dispatch(dbus_conn);
-#endif
-
- cras_alert_process_all_pending_alerts();
- }
-
-bail:
- cleanup_server_sockets();
- free(pollfds);
- cras_observer_server_free();
- return rc;
-}
-
-void cras_server_send_to_all_clients(const struct cras_client_message *msg)
-{
- struct attached_client *client;
-
- DL_FOREACH (server_instance.clients_head, client)
- cras_rclient_send_message(client->client, msg, NULL, 0);
-}