/* 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_config.h" #include "cras_device_monitor.h" #include "cras_iodev_list.h" #include "cras_main_message.h" #include "cras_messages.h" #include "cras_metrics.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_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. * callack - The funciton to call when fd is ready. * callback_data - Pointer passed to the callback. * pollfd - Pointer to struct pollfd for this callback. */ struct client_callback { int select_fd; void (*callback)(void *); void *callback_data; struct pollfd *pollfd; int deleted; struct client_callback *prev, *next; }; /* Local server data. */ struct server_data { struct attached_client *clients_head; size_t num_clients; struct client_callback *client_callbacks; size_t num_client_callbacks; size_t next_client_id; } server_instance; /* 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]; struct cras_server_message *msg; int nread; int fd; unsigned int num_fds = 1; msg = (struct cras_server_message *)buf; nread = cras_recv_with_fds(client->fd, buf, sizeof(buf), &fd, &num_fds); if (nread < sizeof(msg->length)) goto read_error; if (msg->length != nread) goto read_error; cras_rclient_message_from_client(client->client, msg, fd); return; read_error: if (fd != -1) close(fd); 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 sockaddr_un *address, int fd) { 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(fd, (struct sockaddr *) address, &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); if (poll_client->client == NULL) { syslog(LOG_ERR, "failed to create client"); close(connection_fd); free(poll_client); return; } 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); } /* 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), void *callback_data, 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->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; } /* 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) { __asm__ __volatile__ ( "cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "a" (op), "c" (0) ); } 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); /* 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_main_message_init(); return 0; } 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 socket_fd = -1; int rc = 0; const char *sockdir; struct sockaddr_un addr; struct attached_client *elm; struct client_callback *client_cb; struct cras_tm *tm; struct timespec ts; 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(); #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 socket_fd = socket(PF_UNIX, SOCK_SEQPACKET, 0); if (socket_fd < 0) { syslog(LOG_ERR, "Main server socket failed."); rc = socket_fd; goto bail; } sockdir = cras_config_get_system_socket_file_dir(); if (sockdir == NULL) { rc = -ENOTDIR; goto bail; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", sockdir, CRAS_SOCKET_FILE); 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 bail; if (bind(socket_fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) != 0) { syslog(LOG_ERR, "Bind to server socket failed."); rc = errno; goto bail; } /* Let other members in our group play audio through this socket. */ rc = chmod(addr.sun_path, 0770); if (rc < 0) goto bail; if (listen(socket_fd, 5) != 0) { syslog(LOG_ERR, "Listen on server socket failed."); rc = errno; 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 = 1 + 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); } pollfds[0].fd = socket_fd; pollfds[0].events = POLLIN; num_pollfds = 1; 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 = POLLIN; client_cb->pollfd = &pollfds[num_pollfds]; num_pollfds++; } timers_active = cras_tm_get_next_timeout(tm, &ts); rc = ppoll(pollfds, num_pollfds, timers_active ? &ts : NULL, NULL); if (rc < 0) continue; cras_tm_call_callbacks(tm); /* Check for new connections. */ if (pollfds[0].revents & POLLIN) handle_new_connection(&addr, socket_fd); /* 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 & POLLIN)) client_cb->callback(client_cb->callback_data); cleanup_select_fds(&server_instance); #ifdef CRAS_DBUS if (dbus_conn) cras_dbus_dispatch(dbus_conn); #endif cras_alert_process_all_pending_alerts(); } bail: if (socket_fd >= 0) { close(socket_fd); unlink(addr.sun_path); } 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); }