1 /* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
3 /** \file audiod_command.c Commands for para_audiod. */
5 #include <netinet/in.h>
6 #include <sys/socket.h>
14 #include "audiod.lsg.h"
17 #include "audiod_cmd.lsg.h"
20 #include "buffer_tree.h"
22 #include "grab_client.h"
32 extern struct sched sched
;
33 extern char *stat_item_values
[NUM_STAT_ITEMS
];
35 /** The maximal number of simultaneous connections. */
36 #define MAX_STAT_CLIENTS 50
38 /** Pointer to a command handler function. */
39 typedef int (*audiod_cmd_handler_t
)(int, struct lls_parse_result
*);
41 /** The lopsub user_data pointer. Only the command handler at the moment. */
42 struct audiod_command_info
{
43 audiod_cmd_handler_t handler
; /**< Implementation of the command. */
46 /** Define the user_data pointer as expected by lopsub. */
47 #define EXPORT_AUDIOD_CMD_HANDLER(_cmd) \
48 /** Implementation of _cmd. */ \
49 const struct audiod_command_info lsg_audiod_cmd_com_ ## _cmd ## _user_data = { \
50 .handler = com_ ## _cmd \
53 /** Flags used for the stat command of para_audiod. */
54 enum stat_client_flags
{
55 /** Enable parser-friendly output. */
56 SCF_PARSER_FRIENDLY
= 1,
60 * Describes a status client of para_audiod.
62 * There's one such structure per audiod client that sent the 'stat' command.
64 * A status client is identified by its file descriptor. para_audiod
65 * keeps a list of connected status clients.
68 /** The stat client's file descriptor. */
70 /** Bitmask of those status items the client is interested in. */
72 /** See \ref stat_client flags. */
74 /** Its entry in the list of stat clients. */
75 struct list_head node
;
78 static INITIALIZED_LIST_HEAD(client_list
);
79 static int num_clients
;
81 /** The list of all status items used by para_{server,audiod,gui}. */
82 const char *status_item_list
[] = {STATUS_ITEMS
};
84 static void dump_stat_client_list(void)
86 struct stat_client
*sc
;
88 list_for_each_entry(sc
, &client_list
, node
)
89 PARA_INFO_LOG("stat client on fd %d\n", sc
->fd
);
92 * Add a status client to the list.
94 * \param fd The file descriptor of the client.
95 * \param mask Bitfield of status items for this client.
96 * \param parser_friendly Enable parser-friendly output mode.
98 * Only those status items having the bit set in \a mask will be
101 * \return Positive value on success, or -E_TOO_MANY_CLIENTS if
102 * the number of connected clients exceeds #MAX_STAT_CLIENTS.
104 static int stat_client_add(int fd
, uint64_t mask
, int parser_friendly
)
106 struct stat_client
*new_client
;
109 if (num_clients
>= MAX_STAT_CLIENTS
) {
110 PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n",
112 return -E_TOO_MANY_CLIENTS
;
116 return -ERRNO_TO_PARA_ERROR(errno
);
117 new_client
= para_calloc(sizeof(*new_client
));
118 new_client
->fd
= ret
;
119 PARA_INFO_LOG("adding client on fd %d\n", new_client
->fd
);
120 new_client
->item_mask
= mask
;
122 new_client
->flags
= SCF_PARSER_FRIENDLY
;
123 para_list_add(&new_client
->node
, &client_list
);
124 dump_stat_client_list();
129 static void close_stat_client(struct stat_client
*sc
)
131 PARA_INFO_LOG("closing client fd %d\n", sc
->fd
);
139 * Empty the status clients list.
141 * This iterates over the list of connected status clients, closes each client
142 * file descriptor and frees the resources.
144 void close_stat_clients(void)
146 struct stat_client
*sc
, *tmp
;
148 list_for_each_entry_safe(sc
, tmp
, &client_list
, node
)
149 close_stat_client(sc
);
150 assert(num_clients
== 0);
154 * Write a message to all connected status clients.
156 * \param item_num The number of the status item of \a msg.
158 * On write errors, remove the status client from the client list and close its
161 void stat_client_write_item(int item_num
)
163 struct stat_client
*sc
, *tmp
;
164 struct para_buffer pb
= {.flags
= 0};
165 struct para_buffer pfpb
= {.flags
= PBF_SIZE_PREFIX
};
166 const uint64_t one
= 1;
167 char *msg
= stat_item_values
[item_num
];
168 struct para_buffer
*b
;
170 list_for_each_entry_safe(sc
, tmp
, &client_list
, node
) {
173 if (!((one
<< item_num
) & sc
->item_mask
))
175 b
= (sc
->flags
& SCF_PARSER_FRIENDLY
)? &pfpb
: &pb
;
177 WRITE_STATUS_ITEM(b
, item_num
, "%s\n", msg
? msg
: "");
178 ret
= write(sc
->fd
, b
->buf
, b
->offset
);
179 if (ret
== b
->offset
)
181 /* write error or short write */
182 close_stat_client(sc
);
183 dump_stat_client_list();
190 * Check if string is a known status item.
192 * \param item Buffer containing the text to check.
194 * \return If \a item is a valid status item, the number of that status item is
195 * returned. Otherwise, this function returns \p -E_UNKNOWN_STAT_ITEM.
197 static int stat_item_valid(const char *item
)
200 if (!item
|| !*item
) {
201 PARA_ERROR_LOG("%s\n", "no item");
202 return -E_UNKNOWN_STAT_ITEM
;
204 FOR_EACH_STATUS_ITEM(i
)
205 if (!strcmp(status_item_list
[i
], item
))
207 PARA_ERROR_LOG("invalid stat item: %s\n", item
);
208 return -E_UNKNOWN_STAT_ITEM
;
211 static int client_write(int fd
, const char *buf
)
218 return write(fd
, buf
, len
) != len
? -E_CLIENT_WRITE
: 1;
221 __malloc
static char *audiod_status_string(void)
223 const char *status
= (audiod_status
== AUDIOD_ON
)?
224 "on" : (audiod_status
== AUDIOD_OFF
)? "off": "sb";
225 return para_strdup(status
);
228 static int com_help(int fd
, struct lls_parse_result
*lpr
)
232 const struct lls_opt_result
*r
=
233 lls_opt_result(LSG_AUDIOD_CMD_HELP_OPT_LONG
, lpr
);
234 bool long_help
= lls_opt_given(r
);
236 lsu_com_help(long_help
, lpr
, audiod_cmd_suite
, NULL
, &buf
, NULL
);
237 ret
= client_write(fd
, buf
);
239 return ret
< 0? ret
: 0;
241 EXPORT_AUDIOD_CMD_HANDLER(help
)
243 static int com_tasks(int fd
, __a_unused
struct lls_parse_result
*lpr
)
246 char *tl
= get_task_list(&sched
);
248 if (!tl
) /* no tasks registered yet */
250 ret
= client_write(fd
, tl
);
254 EXPORT_AUDIOD_CMD_HANDLER(tasks
)
256 static int com_stat(int fd
, struct lls_parse_result
*lpr
)
258 int i
, ret
, parser_friendly
= 0;
260 const uint64_t one
= 1;
261 struct para_buffer b
= {.flags
= 0};
262 const struct lls_opt_result
*r
;
265 ret
= mark_fd_nonblocking(fd
);
268 r
= lls_opt_result(LSG_AUDIOD_CMD_STAT_OPT_PARSER_FRIENDLY
, lpr
);
269 if (lls_opt_given(r
) > 0) {
271 b
.flags
= PBF_SIZE_PREFIX
;
273 num_inputs
= lls_num_inputs(lpr
);
275 mask
--; /* set all bits */
276 for (i
= 0; i
< num_inputs
; i
++) {
277 ret
= stat_item_valid(lls_input(i
, lpr
));
280 mask
|= (one
<< ret
);
282 PARA_INFO_LOG("mask: 0x%llx\n", (long long unsigned)mask
);
283 FOR_EACH_STATUS_ITEM(i
) {
284 char *item
= stat_item_values
[i
];
285 if (!((one
<< i
) & mask
))
287 WRITE_STATUS_ITEM(&b
, i
, "%s\n", item
? item
: "");
289 ret
= client_write(fd
, b
.buf
);
291 ret
= stat_client_add(fd
, mask
, parser_friendly
);
293 return ret
< 0? ret
: 0;
295 EXPORT_AUDIOD_CMD_HANDLER(stat
)
297 static int com_grab(int fd
, struct lls_parse_result
*lpr
)
299 int ret
= grab_client_new(fd
, lpr
, &sched
);
300 return ret
< 0? ret
: 0;
302 EXPORT_AUDIOD_CMD_HANDLER(grab
)
304 static int com_term(__a_unused
int fd
, __a_unused
struct lls_parse_result
*lpr
)
306 return -E_AUDIOD_TERM
;
308 EXPORT_AUDIOD_CMD_HANDLER(term
)
310 static int com_on(__a_unused
int fd
, __a_unused
struct lls_parse_result
*lpr
)
312 audiod_status
= AUDIOD_ON
;
315 EXPORT_AUDIOD_CMD_HANDLER(on
)
317 static int com_off(__a_unused
int fd
, __a_unused
struct lls_parse_result
*lpr
)
319 audiod_status
= AUDIOD_OFF
;
322 EXPORT_AUDIOD_CMD_HANDLER(off
)
324 static int com_sb(__a_unused
int fd
, __a_unused
struct lls_parse_result
*lpr
)
326 audiod_status
= AUDIOD_STANDBY
;
329 EXPORT_AUDIOD_CMD_HANDLER(sb
)
331 static int com_cycle(__a_unused
int fd
, __a_unused
struct lls_parse_result
*lpr
)
333 switch (audiod_status
) {
334 case AUDIOD_ON
: audiod_status
= AUDIOD_STANDBY
; break;
335 case AUDIOD_OFF
: audiod_status
= AUDIOD_ON
; break;
336 case AUDIOD_STANDBY
: audiod_status
= AUDIOD_OFF
; break;
340 EXPORT_AUDIOD_CMD_HANDLER(cycle
)
342 static int com_version(int fd
, struct lls_parse_result
*lpr
)
346 const struct lls_opt_result
*r_v
;
348 r_v
= lls_opt_result(LSG_AUDIOD_CMD_VERSION_OPT_VERBOSE
, lpr
);
349 if (lls_opt_given(r_v
))
350 msg
= make_message("%s", version_text("audiod"));
352 msg
= make_message("%s\n", version_single_line("audiod"));
353 ret
= client_write(fd
, msg
);
355 return ret
< 0? ret
: 0;
357 EXPORT_AUDIOD_CMD_HANDLER(version
)
360 * Handle arriving connections on the local socket.
362 * \param accept_fd The fd to accept connections on.
363 * \param rfds If \a accept_fd is not set in \a rfds, do nothing.
365 * This is called in each iteration of the select loop. If there is an incoming
366 * connection on \a accept_fd, this function reads the command sent by the peer,
367 * checks the connecting user's permissions by using unix socket credentials
368 * (if supported by the OS) and calls the corresponding command handler if
369 * permissions are OK.
371 * \return Positive on success, negative on errors, zero if there was no
372 * connection to accept.
374 * \sa \ref para_accept(), \ref recv_cred_buffer().
376 int handle_connect(int accept_fd
, fd_set
*rfds
)
378 int argc
, ret
, clifd
;
379 char buf
[MAXLINE
], **argv
= NULL
;
380 struct sockaddr_un unix_addr
;
382 const struct lls_command
*cmd
;
383 struct lls_parse_result
*lpr
;
385 const struct audiod_command_info
*aci
;
387 ret
= para_accept(accept_fd
, rfds
, &unix_addr
, sizeof(struct sockaddr_un
), &clifd
);
390 ret
= recv_cred_buffer(clifd
, buf
, sizeof(buf
) - 1);
394 PARA_INFO_LOG("connection from UID %d, buf: %s\n", ret
, buf
);
396 if (!uid_is_whitelisted(uid
))
398 ret
= create_argv(buf
, "\n", &argv
);
402 ret
= lls(lls_lookup_subcmd(argv
[0], audiod_cmd_suite
, &errctx
));
405 cmd
= lls_cmd(ret
, audiod_cmd_suite
);
406 ret
= lls(lls_parse(argc
, argv
, cmd
, &lpr
, &errctx
));
409 aci
= lls_user_data(cmd
);
410 ret
= aci
->handler(clifd
, lpr
);
411 lls_free_parse_result(lpr
, cmd
);
414 if (ret
< 0 && ret
!= -E_CLIENT_WRITE
) {
417 tmp
= make_message("%s\n", errctx
);
419 client_write(clifd
, tmp
);
422 tmp
= make_message("%s\n", para_strerror(-ret
));
423 client_write(clifd
, tmp
);
431 * Send the current audiod status to all connected stat clients.
433 * \param force Whether to write unchanged items.
435 void audiod_status_dump(bool force
)
439 old
= stat_item_values
[SI_play_time
];
440 new = get_time_string();
442 if (force
|| !old
|| strcmp(old
, new)) {
444 stat_item_values
[SI_play_time
] = new;
445 stat_client_write_item(SI_play_time
);
450 new = daemon_get_uptime_str(now
);
451 old
= stat_item_values
[SI_audiod_uptime
];
452 if (force
|| !old
|| strcmp(old
, new)) {
454 stat_item_values
[SI_audiod_uptime
] = new;
455 stat_client_write_item(SI_audiod_uptime
);
459 old
= stat_item_values
[SI_audiod_status
];
460 new = audiod_status_string();
461 if (force
|| !old
|| strcmp(old
, new)) {
463 stat_item_values
[SI_audiod_status
] = new;
464 stat_client_write_item(SI_audiod_status
);
468 old
= stat_item_values
[SI_decoder_flags
];
469 new = audiod_get_decoder_flags();
470 if (force
|| !old
|| strcmp(old
, new)) {
472 stat_item_values
[SI_decoder_flags
] = new;
473 stat_client_write_item(SI_decoder_flags
);
479 * Flush and send all status items.
481 * Send to each connected client the full status item list
484 void clear_and_dump_items(void)
488 FOR_EACH_STATUS_ITEM(i
) {
489 free(stat_item_values
[i
]);
490 stat_item_values
[i
] = NULL
;
491 stat_client_write_item(i
);