+ /* ignore subsequent errors (but log them) */
+ para_printf(&aca->pbout, "could not activate %s\n", arg);
+ if (current_mop) {
+ int ret2;
+ para_printf(&aca->pbout, "switching back to %s\n", current_mop);
+ ret2 = activate_mood_or_playlist(current_mop, &num_admissible);
+ if (ret2 >= 0)
+ goto out;
+ para_printf(&aca->pbout, "could not reactivate %s: %s\n",
+ current_mop, para_strerror(-ret2));
+ }
+ para_printf(&aca->pbout, "activating dummy mood\n");
+ activate_mood_or_playlist(NULL, &num_admissible);
+out:
+ para_printf(&aca->pbout, "activated %s (%d admissible files)\n",
+ current_mop? current_mop : "dummy mood", num_admissible);
+ return ret;
+}
+
+int com_select(struct command_context *cc)
+{
+ struct osl_object query;
+
+ if (cc->argc != 2)
+ return -E_AFS_SYNTAX;
+ query.data = cc->argv[1];
+ query.size = strlen(cc->argv[1]) + 1;
+ return send_callback_request(com_select_callback, &query,
+ &afs_cb_result_handler, cc);
+}
+
+static void init_admissible_files(char *arg)
+{
+ if (activate_mood_or_playlist(arg, NULL) < 0)
+ activate_mood_or_playlist(NULL, NULL); /* always successful */
+}
+
+static int setup_command_socket_or_die(void)
+{
+ int ret, socket_fd;
+ char *socket_name = conf.afs_socket_arg;
+
+ unlink(socket_name);
+ ret = create_local_socket(socket_name, 0);
+ if (ret < 0) {
+ ret = create_local_socket(socket_name,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IROTH);
+ if (ret < 0) {
+ PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret),
+ socket_name);
+ exit(EXIT_FAILURE);
+ }
+ }
+ socket_fd = ret;
+ PARA_INFO_LOG("listening on socket %s (fd %d)\n", socket_name,
+ socket_fd);
+ return socket_fd;
+}
+
+static void close_afs_tables(void)
+{
+ int i;
+ PARA_NOTICE_LOG("closing afs_tables\n");
+ for (i = 0; i < NUM_AFS_TABLES; i++)
+ afs_tables[i].close();
+}
+
+static char *database_dir;
+
+static void get_database_dir(void)
+{
+ if (!database_dir) {
+ if (conf.afs_database_dir_given)
+ database_dir = para_strdup(conf.afs_database_dir_arg);
+ else {
+ char *home = para_homedir();
+ database_dir = make_message(
+ "%s/.paraslash/afs_database-0.4", home);
+ free(home);
+ }
+ }
+ PARA_INFO_LOG("afs_database dir %s\n", database_dir);
+}
+
+static int make_database_dir(void)
+{
+ int ret;
+
+ get_database_dir();
+ ret = para_mkdir(database_dir, 0777);
+ if (ret >= 0 || ret == -ERRNO_TO_PARA_ERROR(EEXIST))
+ return 1;
+ return ret;
+}
+
+static int open_afs_tables(void)
+{
+ int i, ret;
+
+ get_database_dir();
+ PARA_NOTICE_LOG("opening %d osl tables in %s\n", NUM_AFS_TABLES,
+ database_dir);
+ for (i = 0; i < NUM_AFS_TABLES; i++) {
+ ret = afs_tables[i].open(database_dir);
+ if (ret >= 0)
+ continue;
+ PARA_ERROR_LOG("%s init: %s\n", afs_tables[i].name,
+ para_strerror(-ret));
+ break;
+ }
+ if (ret >= 0)
+ return ret;
+ while (i)
+ afs_tables[--i].close();
+ return ret;
+}
+
+static int afs_signal_post_select(struct sched *s, __a_unused void *context)
+{
+ int signum, ret;
+
+ if (getppid() == 1) {
+ PARA_EMERG_LOG("para_server died\n");
+ goto shutdown;
+ }
+ signum = para_next_signal(&s->rfds);
+ if (signum == 0)
+ return 0;
+ if (signum == SIGHUP) {
+ close_afs_tables();
+ parse_config_or_die(1);
+ ret = open_afs_tables();
+ if (ret < 0)
+ return ret;
+ init_admissible_files(current_mop);
+ return 0;
+ }
+ PARA_EMERG_LOG("terminating on signal %d\n", signum);
+shutdown:
+ task_notify_all(s, E_AFS_SIGNAL);
+ return -E_AFS_SIGNAL;
+}
+
+static void register_signal_task(struct sched *s)
+{
+ para_sigaction(SIGPIPE, SIG_IGN);
+ signal_task = signal_init_or_die();
+ para_install_sighandler(SIGINT);
+ para_install_sighandler(SIGTERM);
+ para_install_sighandler(SIGHUP);
+
+ signal_task->task = task_register(&(struct task_info) {
+ .name = "signal",
+ .pre_select = signal_pre_select,
+ .post_select = afs_signal_post_select,
+ .context = signal_task,
+
+ }, s);
+}
+
+static struct list_head afs_client_list;
+
+/** Describes one connected afs client. */
+struct afs_client {
+ /** Position in the afs client list. */
+ struct list_head node;
+ /** The socket file descriptor for this client. */
+ int fd;
+ /** The time the client connected. */
+ struct timeval connect_time;
+};
+
+static void command_pre_select(struct sched *s, void *context)
+{
+ struct command_task *ct = context;
+ struct afs_client *client;
+
+ para_fd_set(server_socket, &s->rfds, &s->max_fileno);
+ para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
+ list_for_each_entry(client, &afs_client_list, node)
+ para_fd_set(client->fd, &s->rfds, &s->max_fileno);
+}
+
+/**
+ * Send data as shared memory to a file descriptor.
+ *
+ * \param fd File descriptor to send the shmid to.
+ * \param band The band designator for this data.
+ * \param buf The buffer holding the data to be sent.
+ * \param size The size of \a buf.
+ *
+ * This function creates a shared memory area large enough to hold
+ * the content given by \a buf and \a size and sends the identifier
+ * of this area to the file descriptor \a fd.
+ *
+ * It is called by the AFS max_size handler as well as directly by the AFS
+ * command callbacks to send command output to the command handlers.
+ *
+ * \return Zero if \a buf is \p NULL or \a size is zero. Negative on errors,
+ * and positive on success.
+ */
+int pass_buffer_as_shm(int fd, uint8_t band, const char *buf, size_t size)
+{
+ int ret, shmid;
+ void *shm;
+ struct callback_result *cr;
+
+ if (size == 0)
+ assert(band != SBD_OUTPUT);
+ ret = shm_new(size + sizeof(*cr));
+ if (ret < 0)
+ return ret;
+ shmid = ret;
+ ret = shm_attach(shmid, ATTACH_RW, &shm);
+ if (ret < 0)
+ goto err;
+ cr = shm;
+ cr->result_size = size;
+ cr->band = band;
+ if (size > 0)
+ memcpy(shm + sizeof(*cr), buf, size);
+ ret = shm_detach(shm);
+ if (ret < 0)
+ goto err;
+ ret = write_all(fd, (char *)&shmid, sizeof(int));
+ if (ret >= 0)
+ return ret;
+err:
+ if (shm_destroy(shmid) < 0)
+ PARA_ERROR_LOG("destroy result failed\n");
+ return ret;
+}
+
+static int call_callback(int fd, int query_shmid)
+{
+ void *query_shm;
+ struct callback_query *cq;
+ int ret, ret2;
+ struct afs_callback_arg aca = {.fd = fd};
+
+ ret = shm_attach(query_shmid, ATTACH_RW, &query_shm);
+ if (ret < 0)
+ return ret;
+ cq = query_shm;
+ aca.query.data = (char *)query_shm + sizeof(*cq);
+ aca.query.size = cq->query_size;
+ aca.pbout.max_size = shm_get_shmmax();
+ aca.pbout.max_size_handler = afs_max_size_handler;
+ aca.pbout.private_data = &(struct afs_max_size_handler_data) {
+ .fd = fd,
+ .band = SBD_OUTPUT
+ };
+ ret = cq->handler(&aca);
+ ret2 = shm_detach(query_shm);
+ if (ret2 < 0) {
+ if (ret < 0) /* ignore (but log) detach error */
+ PARA_ERROR_LOG("could not detach sma: %s\n",
+ para_strerror(-ret2));
+ else
+ ret = ret2;
+ }
+ flush_and_free_pb(&aca.pbout);
+ if (ret < 0) {
+ ret2 = pass_buffer_as_shm(fd, SBD_AFS_CB_FAILURE,
+ (const char *)&ret, sizeof(ret));
+ if (ret2 < 0)
+ PARA_ERROR_LOG("could not pass cb failure packet: %s\n",
+ para_strerror(-ret));
+ }
+ return ret;
+}
+
+static int execute_server_command(fd_set *rfds)
+{
+ char buf[8];
+ size_t n;
+ int ret = read_nonblock(server_socket, buf, sizeof(buf) - 1, rfds, &n);
+
+ if (ret < 0 || n == 0)
+ return ret;
+ buf[n] = '\0';
+ if (strcmp(buf, "new"))
+ return -E_BAD_CMD;
+ return open_next_audio_file();
+}
+
+/* returns 0 if no data available, 1 else */
+static int execute_afs_command(int fd, fd_set *rfds, uint32_t expected_cookie)
+{
+ uint32_t cookie;
+ int query_shmid;
+ char buf[sizeof(cookie) + sizeof(query_shmid)];
+ size_t n;
+ int ret = read_nonblock(fd, buf, sizeof(buf), rfds, &n);
+
+ if (ret < 0)
+ goto err;
+ if (n == 0)
+ return 0;
+ if (n != sizeof(buf)) {
+ PARA_NOTICE_LOG("short read (%d bytes, expected %lu)\n",
+ ret, (long unsigned) sizeof(buf));
+ return 1;
+ }
+ cookie = *(uint32_t *)buf;
+ if (cookie != expected_cookie) {
+ PARA_NOTICE_LOG("received invalid cookie (got %u, expected %u)\n",
+ (unsigned)cookie, (unsigned)expected_cookie);
+ return 1;
+ }
+ query_shmid = *(int *)(buf + sizeof(cookie));
+ if (query_shmid < 0) {
+ PARA_WARNING_LOG("received invalid query shmid %d)\n",
+ query_shmid);
+ return 1;
+ }
+ ret = call_callback(fd, query_shmid);
+ if (ret >= 0)
+ return 1;
+err:
+ PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
+ return 1;
+}
+
+/** Shutdown connection if query has not arrived until this many seconds. */
+#define AFS_CLIENT_TIMEOUT 3
+
+static int command_post_select(struct sched *s, void *context)
+{
+ struct command_task *ct = context;
+ struct sockaddr_un unix_addr;
+ struct afs_client *client, *tmp;
+ int fd, ret;
+
+ ret = task_get_notification(ct->task);
+ if (ret < 0)
+ return ret;
+ ret = execute_server_command(&s->rfds);
+ if (ret < 0) {
+ PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+ task_notify_all(s, -ret);
+ return ret;
+ }
+ /* Check the list of connected clients. */
+ list_for_each_entry_safe(client, tmp, &afs_client_list, node) {
+ ret = execute_afs_command(client->fd, &s->rfds, ct->cookie);
+ if (ret == 0) { /* prevent bogus connection flooding */
+ struct timeval diff;
+ tv_diff(now, &client->connect_time, &diff);
+ if (diff.tv_sec < AFS_CLIENT_TIMEOUT)
+ continue;
+ PARA_WARNING_LOG("connection timeout\n");
+ }
+ close(client->fd);
+ list_del(&client->node);
+ free(client);
+ }
+ /* Accept connections on the local socket. */
+ ret = para_accept(ct->fd, &s->rfds, &unix_addr, sizeof(unix_addr), &fd);
+ if (ret < 0)
+ PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
+ if (ret <= 0)
+ return 0;
+ ret = mark_fd_nonblocking(fd);
+ if (ret < 0) {
+ PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
+ close(fd);
+ return 0;
+ }
+ client = para_malloc(sizeof(*client));
+ client->fd = fd;
+ client->connect_time = *now;
+ para_list_add(&client->node, &afs_client_list);
+ return 0;
+}
+
+static void register_command_task(uint32_t cookie, struct sched *s)
+{
+ struct command_task *ct = &command_task_struct;
+ ct->fd = setup_command_socket_or_die();
+ ct->cookie = cookie;
+
+ ct->task = task_register(&(struct task_info) {
+ .name = "afs command",
+ .pre_select = command_pre_select,
+ .post_select = command_post_select,
+ .context = ct,
+ }, s);
+}
+
+/**
+ * Initialize the audio file selector process.
+ *
+ * \param cookie The value used for "authentication".
+ * \param socket_fd File descriptor used for communication with the server.
+ */
+__noreturn void afs_init(uint32_t cookie, int socket_fd)
+{
+ static struct sched s;
+ int i, ret;
+
+ register_signal_task(&s);
+ INIT_LIST_HEAD(&afs_client_list);
+ for (i = 0; i < NUM_AFS_TABLES; i++)
+ afs_tables[i].init(&afs_tables[i]);
+ ret = open_afs_tables();
+ if (ret < 0)