bce9c964b9730604cd315603673ca98f9c5e454d
[paraslash.git] / grab_client.c
1 /*
2  * Copyright (C) 2006-2009 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /**
8  * \file grab_client.c Functions for grabbing the stream at any position
9  * in a filter chain.
10  *
11  * \sa filter_chain filter_chain_info filter.
12  */
13
14 #include <regex.h>
15 #include <sys/types.h>
16 #include <dirent.h>
17 #include <stdbool.h>
18
19 #include "para.h"
20 #include "list.h"
21 #include "sched.h"
22 #include "ggo.h"
23 #include "buffer_tree.h"
24 #include "filter.h"
25 #include "grab_client.h"
26 #include "audiod.h"
27 #include "error.h"
28 #include "string.h"
29 #include "fd.h"
30
31 /** Grab clients that are not yet attached any btr node. */
32 static struct list_head inactive_grab_client_list;
33
34 /** Grab clients that are attached to a btr node. */
35 static struct list_head active_grab_client_list;
36
37 static int gc_write(struct grab_client *gc, char *buf, size_t len)
38 {
39         int ret = write_ok(gc->fd);
40
41         if (ret < 0)
42                 goto err;
43         if (ret == 0) { /* fd not ready */
44                 if (gc->mode == GM_PEDANTIC)
45                         goto err;
46                 if (gc->mode == GM_SLOPPY)
47                         return len;
48         }
49         ret = write_nonblock(gc->fd, buf, len, 0);
50         if (ret < 0)
51                 goto err;
52         if (ret > 0)
53                 return ret;
54         if (ret == 0) {
55                 if (gc->mode == GM_PEDANTIC)
56                         goto err;
57                 if (gc->mode == GM_SLOPPY)
58                         return len;
59         }
60         return 0;
61 err:
62         return -E_GC_WRITE;
63 }
64
65 static void gc_pre_select(struct sched *s, struct task *t)
66 {
67         struct grab_client *gc = container_of(t, struct grab_client, task);
68         int ret = btr_node_status(gc->btrn, 0, BTR_NT_LEAF);
69
70         if (ret == 0)
71                 return;
72         if (ret < 0) {
73                 s->timeout.tv_sec = 0;
74                 s->timeout.tv_usec = 0;
75                 return;
76         }
77         para_fd_set(gc->fd, &s->wfds, &s->max_fileno);
78 }
79
80 /*
81  * We need this forward declaration as post_select() needs
82  * activate_grab_client and vice versa.
83  */
84 static void gc_post_select(struct sched *s, struct task *t);
85
86 /**
87  * Move a grab client to the active list and start it.
88  *
89  * \param gc The grab client to activate.
90  *
91  */
92 static void activate_grab_client(struct grab_client *gc)
93 {
94         struct btr_node *root = audiod_get_btr_root(), *parent;
95
96         if (!root)
97                 return;
98         parent = btr_search_node(gc->parent, root);
99         if (!parent)
100                 return;
101         PARA_INFO_LOG("activating %p (fd %d)\n", gc, gc->fd);
102         list_move(&gc->node, &active_grab_client_list);
103         gc->btrn = btr_new_node("grab", parent, NULL, NULL);
104         if (!gc->task.pre_select) {
105                 gc->task.pre_select = gc_pre_select;
106                 gc->task.post_select = gc_post_select;
107                 sprintf(gc->task.status, "grab");
108                 register_task(&gc->task);
109         }
110 }
111
112 /**
113  * Activate inactive grab clients if possible.
114  *
115  * This is called from audiod.c when the current audio file changes. It loops
116  * over all inactive grab clients and checks each grab client's configuration
117  * to determine if the client in question wishes to grab the new stream.  If
118  * yes, this grab client is moved from the inactive to the active grab client list.
119  */
120 void activate_grab_clients(void)
121 {
122         struct grab_client *gc, *tmp;
123
124         list_for_each_entry_safe(gc, tmp, &inactive_grab_client_list, node) {
125                 if (gc->task.error == -E_TASK_UNREGISTERED) {
126                         list_del(&gc->node);
127                         free(gc);
128                         continue;
129                 }
130                 activate_grab_client(gc);
131         }
132 }
133
134 static void add_inactive_gc(struct grab_client *gc)
135 {
136         PARA_INFO_LOG("adding grab client %p (fd %d) to inactive list\n",
137                 gc, gc->fd);
138         para_list_add(&gc->node, &inactive_grab_client_list);
139 }
140
141 static int gc_close(struct grab_client *gc, int err)
142 {
143         btr_remove_node(gc->btrn);
144         btr_free_node(gc->btrn);
145         gc->btrn = NULL;
146         PARA_INFO_LOG("closing gc: %s\n", para_strerror(-err));
147         list_move(&gc->node, &inactive_grab_client_list);
148         if (err == -E_GC_WRITE || (gc->flags & GF_ONE_SHOT)) {
149                 close(gc->fd);
150                 free(gc->parent);
151                 return 1;
152         }
153         activate_grab_client(gc);
154         return 0;
155 }
156
157 static void gc_post_select(__a_unused struct sched *s, struct task *t)
158 {
159         struct grab_client *gc = container_of(t, struct grab_client, task);
160         struct btr_node *btrn = gc->btrn;
161         int ret;
162         size_t sz;
163         char *buf;
164
165         t->error = 0;
166         ret = btr_node_status(btrn, 0, BTR_NT_LEAF);
167         if (ret == 0)
168                 return;
169         if (ret < 0)
170                 goto err;
171         sz = btr_next_buffer(btrn, &buf);
172         assert(sz != 0);
173         ret = gc_write(gc, buf, sz);
174         if (ret < 0)
175                 goto err;
176         if (ret > 0)
177                 btr_consume(btrn, ret);
178         return;
179 err:
180         t->error = gc_close(gc, ret)? ret : 0;
181 }
182
183 static int check_gc_args(int argc, char **argv, struct grab_client *gc)
184 {
185         int i;
186
187         for (i = 1; i < argc; i++) {
188                 const char *arg = argv[i];
189                 if (arg[0] != '-')
190                         break;
191                 if (!strcmp(arg, "--")) {
192                         i++;
193                         break;
194                 }
195                 if (!strncmp(arg, "-m", 2)) {
196                         if (*(arg + 3))
197                                 return -E_GC_SYNTAX;
198                         switch(*(arg + 2)) {
199                         case 's':
200                                 gc->mode = GM_SLOPPY;
201                                 continue;
202                         case 'a':
203                                 gc->mode = GM_AGGRESSIVE;
204                                 continue;
205                         case 'p':
206                                 gc->mode = GM_PEDANTIC;
207                                 continue;
208                         default:
209                                 return -E_GC_SYNTAX;
210                         }
211                 }
212                 if (!strcmp(arg, "-o")) {
213                         gc->flags |= GF_ONE_SHOT;
214                         continue;
215                 }
216                 if (!strncmp(arg, "-p=", 3)) {
217                         gc->parent = para_strdup(arg + 3);
218                         continue;
219                 }
220                 return -E_GC_SYNTAX;
221         }
222         if (i != argc)
223                 return -E_GC_SYNTAX;
224         return 1;
225 }
226
227 /**
228  * Check the command line options and allocate a grab_client structure.
229  *
230  * \param fd The file descriptor of the client.
231  * \param argc Argument count.
232  * \param argv Argument vector.
233  *
234  * If the command line options given by \a argc and \a argv are valid.
235  * allocate a struct grab_client and initialize it with this valid
236  * configuration. Moreover, add the new grab client to the inactive list.
237  *
238  * \return Standard.
239  *
240  * \sa grab_client, inactive_grab_client_list, activate_grab_client,
241  * filter_node::callbacks.
242  */
243 int grab_client_new(int fd, int argc, char **argv)
244 {
245         int ret;
246         struct grab_client *gc = para_calloc(sizeof(struct grab_client));
247
248         ret = check_gc_args(argc, argv, gc);
249         if (ret < 0)
250                 goto err_out;
251         gc->fd = fd;
252         add_inactive_gc(gc);
253         activate_grab_client(gc);
254         return 1;
255 err_out:
256         free(gc);
257         return ret;
258 }
259
260 /**
261  * Initialize the grabbing subsystem.
262  *
263  * This has to be called once during startup before any other function from
264  * grab_client.c may be used. It initializes \a inactive_grab_client_list.
265  */
266 void init_grabbing(void)
267 {
268         PARA_INFO_LOG("grab init\n");
269         INIT_LIST_HEAD(&inactive_grab_client_list);
270         INIT_LIST_HEAD(&active_grab_client_list);
271 }