Pass full argument list to mood parsers.
[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
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 }