Merge branch 'master' into next
[paraslash.git] / stat.c
1 /*
2  * Copyright (C) 2005-2009 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /**
8  *  \file stat.c Functions used for sending/receiving the status of para_server
9  *  and para_audiod.
10  */
11
12 #include <regex.h>
13 #include <sys/types.h>
14 #include <dirent.h>
15
16 #include "para.h"
17 #include "close_on_fork.h"
18 #include "list.h"
19 #include "error.h"
20 #include "string.h"
21 #include "fd.h"
22
23 /** The maximal number of simultaneous connections. */
24 #define MAX_STAT_CLIENTS 50
25
26 extern char *stat_item_values[NUM_STAT_ITEMS];
27
28 /** Flags used for the stat command of para_audiod. */
29 enum stat_client_flags {
30         /** Enable parser-friendly output. */
31         SCF_PARSER_FRIENDLY = 1,
32 };
33
34 /**
35  * Describes a status client of para_audiod.
36  *
37  * There's one such structure per audiod client that sent the 'stat' command.
38  *
39  * A status client is identified by its file descriptor.  para_audiod
40  * keeps a list of connected status clients.
41  */
42 struct stat_client {
43         /** The stat client's file descriptor. */
44         int fd;
45         /** Bitmask of those status items the client is interested in. */
46         uint64_t item_mask;
47         /** See \ref stat_client flags. s*/
48         unsigned flags;
49         /** Its entry in the list of stat clients. */
50         struct list_head node;
51 };
52
53 static struct list_head client_list;
54 static int initialized;
55 static int num_clients;
56
57 /** The list of all status items used by para_{server,audiod,gui}. */
58 const char *status_item_list[] = {STATUS_ITEM_ARRAY};
59
60 static void dump_stat_client_list(void)
61 {
62         struct stat_client *sc;
63
64         if (!initialized)
65                 return;
66         list_for_each_entry(sc, &client_list, node)
67                 PARA_INFO_LOG("stat client on fd %d\n", sc->fd);
68 }
69 /**
70  * Add a status client to the list.
71  *
72  * \param fd The file descriptor of the client.
73  * \param mask Bitfield of status items for this client.
74  * \param parser_friendly Enable parser-friendly output mode.
75  *
76  * Only those status items having the bit set in \a mask will be
77  * sent to the client.
78  *
79  * \return Positive value on success, or -E_TOO_MANY_CLIENTS if
80  * the number of connected clients exceeds #MAX_STAT_CLIENTS.
81  */
82 int stat_client_add(int fd, uint64_t mask, int parser_friendly)
83 {
84         struct stat_client *new_client;
85
86         if (num_clients >= MAX_STAT_CLIENTS) {
87                 PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n",
88                         MAX_STAT_CLIENTS);
89                 return -E_TOO_MANY_CLIENTS;
90         }
91         if (!initialized) {
92                 INIT_LIST_HEAD(&client_list);
93                 initialized = 1;
94         }
95         PARA_INFO_LOG("adding client on fd %d\n", fd);
96         new_client = para_calloc(sizeof(struct stat_client));
97         new_client->fd = fd;
98         new_client->item_mask = mask;
99         if (parser_friendly)
100                 new_client->flags = SCF_PARSER_FRIENDLY;
101         para_list_add(&new_client->node, &client_list);
102         dump_stat_client_list();
103         num_clients++;
104         return 1;
105 }
106 /**
107  * Write a message to all connected status clients.
108  *
109  * \param item_num The number of the status item of \a msg.
110  *
111  * On write errors, remove the status client from the client list and close its
112  * file descriptor.
113  */
114 void stat_client_write_item(int item_num)
115 {
116         struct stat_client *sc, *tmp;
117         struct para_buffer pb = {.flags = 0};
118         struct para_buffer pfpb = {.flags = PBF_SIZE_PREFIX};
119         const uint64_t one = 1;
120
121         if (!initialized)
122                 return;
123         list_for_each_entry_safe(sc, tmp, &client_list, node) {
124                 int fd = sc->fd, ret;
125
126                 if (!((one << item_num) & sc->item_mask))
127                         continue;
128                 if (write_ok(fd) > 0) {
129                         struct para_buffer *b =
130                                 (sc->flags & SCF_PARSER_FRIENDLY)? &pfpb : &pb;
131                         char *msg = stat_item_values[item_num];
132                         if (!b->buf)
133                                 WRITE_STATUS_ITEM(b, item_num, "%s\n",
134                                         msg? msg : "");
135                         ret = write(fd, b->buf, b->offset);
136                         if (ret == b->offset)
137                                 continue;
138                 }
139                 /* write error or fd not ready for writing */
140                 close(fd);
141                 num_clients--;
142                 PARA_INFO_LOG("deleting client on fd %d\n", fd);
143                 list_del(&sc->node);
144                 free(sc);
145                 dump_stat_client_list();
146         }
147         free(pb.buf);
148         free(pfpb.buf);
149 //      if (num_clients)
150 //              PARA_DEBUG_LOG("%d client(s)\n", num_clients);
151 }
152
153 /**
154  * Check if string is a known status item.
155  *
156  * \param item Buffer containing the text to check.
157  *
158  * \return If \a item is a valid status item, the number of that status item is
159  * returned. Otherwise, this function returns \p -E_UNKNOWN_STAT_ITEM.
160  */
161 int stat_item_valid(const char *item)
162 {
163         int i;
164         if (!item || !*item) {
165                 PARA_ERROR_LOG("%s\n", "no item");
166                 return -E_UNKNOWN_STAT_ITEM;
167         }
168         FOR_EACH_STATUS_ITEM(i)
169                 if (!strcmp(status_item_list[i], item))
170                         return i;
171         PARA_ERROR_LOG("invalid stat item: %s\n", item);
172         return -E_UNKNOWN_STAT_ITEM;
173 }
174
175 /** The minimal length of a status item buffer. */
176 #define MIN_STAT_ITEM_LEN 9 /* 5 + 2 + 2, e.g. '0005 00:\n' */
177
178 /**
179  * Call a function for each complete status item of a buffer.
180  *
181  * \param item_buf The source buffer.
182  * \param num_bytes The length of \a buf.
183  * \param item_handler Function to call for each complete item.
184  *
185  * \return Negative on errors, the number of bytes _not_ passed to \a
186  * item_handler.
187  *
188  * Status items are expected in the format used by parser-friendly output mode
189  * of the stat command of para_client/para_audioc.
190  */
191 int for_each_stat_item(char *item_buf, size_t num_bytes,
192         int (*item_handler)(int, char *))
193 {
194         char *buf = item_buf;
195         int len = num_bytes;
196
197         for (;;) {
198                 int i, ret, item_len, item_num = 0;
199                 if (len < MIN_STAT_ITEM_LEN)
200                         break;
201                 ret = read_size_header(buf);
202                 if (ret < 0)
203                         return ret;
204                 item_len = ret;
205                 if (item_len > len - 5) /* item not complete */
206                         break;
207                 for (i = 0; i < 2; i++) {
208                         unsigned char c = buf[5 + i];
209                         item_num <<= 4;
210                         if (c >= '0' && c <= '9') {
211                                 item_num += c - '0';
212                                 continue;
213                         }
214                         if (c >= 'a' && c <= 'f') {
215                                 item_num += c - 'a' + 10;
216                                 continue;
217                         }
218                         return -E_STAT_ITEM_PARSE;
219                 }
220                 if (buf[7] != ':' || buf[5 + item_len - 1] != '\n')
221                         return -E_STAT_ITEM_PARSE;
222                 buf[5 + item_len - 1] = '\0';
223                 if (item_num >= NUM_STAT_ITEMS)
224                         PARA_WARNING_LOG("unknown status item %d: %s\n",
225                                 item_num, buf + 8);
226                 else {
227                         ret = item_handler(item_num, buf + 8);
228                         if (ret < 0)
229                                 return ret;
230                 }
231                 buf += 5 + item_len;
232                 len -= 5 + item_len;
233                 assert(len >= 0 && buf <= item_buf + num_bytes);
234         }
235         assert(len >= 0);
236         if (len && len != num_bytes)
237                 memmove(item_buf, item_buf + num_bytes - len, len);
238         return len;
239 }