From 337d7784b0f8bc30b1fa9af56ace503acd97d40d Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sat, 4 Jul 2009 15:52:39 +0200 Subject: [PATCH] Revamp status item handling. This patch changes the way the status items are printed and parsed. The new parser-friendly format is used internally and, as a side effect, is also available as an new listing mode for the ls command and for the stat commands of both para_server and para_audiod. With the new format, each status item is prefixed by its size, and the status item identifier is printed as a numerical value. This simplifies the parsing code in para_audiod/para_gui a bit and should make it perform better as the status items do not have to be looked up by name. More importantly, the new format gets rid of the restriction that status items should not contain newline characters because the parser knows in advance how much it has to read to get the complete item. This restriction became a real problem as more and more audio files contain (version 2 id3) tags that contain much more than a single line. For example the linux podcast mp3 files contain the full content of the podcast as text in the command tag. --- afs.cmd | 2 + afs.h | 2 +- aft.c | 287 +++++++++++++++++++++++++++------------------- amp_filter.c | 8 +- audiod.c | 79 +++++-------- audiod.cmd | 5 +- audiod.h | 1 - audiod_command.c | 125 ++++++++------------ command.c | 180 ++++++++++++++--------------- compress_filter.c | 2 - error.h | 2 + gui.c | 74 ++++++------ para.h | 8 +- server.cmd | 13 ++- stat.c | 124 ++++++++++++++------ string.c | 62 ++++++++-- string.h | 33 +++++- 17 files changed, 568 insertions(+), 439 deletions(-) diff --git a/afs.cmd b/afs.cmd index 97bf0035..293b5b7e 100644 --- a/afs.cmd +++ b/afs.cmd @@ -55,6 +55,8 @@ H: -ll: long listing mode (equivalent to -l) H: H: -lv: verbose listing mode H: +H: -lp: parser-friendly mode +H: H: -lm: mbox listing mode H: H: -lc: chunk-table listing mode diff --git a/afs.h b/afs.h index 269927f4..d25b7043 100644 --- a/afs.h +++ b/afs.h @@ -117,7 +117,7 @@ struct ls_data { HASH_TYPE *hash; }; -int send_afs_status(int fd); +int send_afs_status(int fd, int parser_friendly); /** Data about the current audio file, passed from afs to server. */ struct audio_file_data { diff --git a/aft.c b/aft.c index 1f7332c8..5ad5d8f0 100644 --- a/aft.c +++ b/aft.c @@ -24,7 +24,8 @@ #include "portable_io.h" static struct osl_table *audio_file_table; -static char *current_status_items; +static char *status_items; +static char *parser_friendly_status_items; /** The different sorting methods of the ls command. */ enum ls_sorting_method { @@ -65,7 +66,9 @@ enum ls_listing_mode { /** -lm */ LS_MODE_MBOX, /** -lc */ - LS_MODE_CHUNKS + LS_MODE_CHUNKS, + /** -lp */ + LS_MODE_PARSER, }; /** The flags accepted by the ls command. */ @@ -412,7 +415,7 @@ static void load_afhi(const char *buf, struct afh_info *afhi) afhi->header_offset = read_u32(buf + HEADER_OFFSET_OFFSET); afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET); afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC_OFFSET); - afhi->techinfo = buf + AFHI_INFO_STRING_OFFSET; + afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET; afhi->tags.artist = afhi->techinfo + strlen(afhi->techinfo) + 1; afhi->tags.title = afhi->tags.artist + strlen(afhi->tags.artist) + 1; afhi->tags.year = afhi->tags.title + strlen(afhi->tags.title) + 1; @@ -723,57 +726,68 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts) } } -static int make_attribute_lines(const char *att_bitmap, struct afs_info *afsi, - char **result) + +static int write_attribute_items(struct para_buffer *b, + const char *att_bitmap, struct afs_info *afsi) { char *att_text; - int ret = get_attribute_text(&afsi->attributes, " ", &att_text); + int ret; + ret = WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_BITMAP, "%s\n", att_bitmap); + if (ret < 0) + return ret; + ret = get_attribute_text(&afsi->attributes, " ", &att_text); if (ret < 0) return ret; - *result = make_message("%s: %s\n%s: %s", - status_item_list[SI_ATTRIBUTES_BITMAP], att_bitmap, - status_item_list[SI_ATTRIBUTES_TXT], att_text); + ret = WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_TXT, "%s\n", att_text); free(att_text); - return 1; + return ret; } -static char *make_lyrics_lines(struct afs_info *afsi) +static int write_lyrics_items(struct para_buffer *b, struct afs_info *afsi) { char *lyrics_name; + int ret; + ret = WRITE_STATUS_ITEM(b, SI_LYRICS_ID, "%u\n", afsi->lyrics_id); + if (ret < 0) + return ret; lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name); - return make_message("%s: %u\n%s: %s\n", - status_item_list[SI_LYRICS_ID], afsi->lyrics_id, - status_item_list[SI_LYRICS_NAME], lyrics_name? - lyrics_name : "(none)"); + return WRITE_STATUS_ITEM(b, SI_LYRICS_NAME, "%s\n", lyrics_name? + lyrics_name : "(none)"); } -static char *make_image_lines(struct afs_info *afsi) +static int write_image_items(struct para_buffer *b, struct afs_info *afsi) { char *image_name; + int ret; + + ret = WRITE_STATUS_ITEM(b, SI_IMAGE_ID, "%u\n", afsi->image_id); + if (ret < 0) + return ret; img_get_name_by_id(afsi->image_id, &image_name); - return make_message("%s: %u\n%s: %s\n", - status_item_list[SI_IMAGE_ID], afsi->image_id, - status_item_list[SI_IMAGE_NAME], image_name? - image_name : "(none)"); + return WRITE_STATUS_ITEM(b, SI_IMAGE_NAME, "%s\n", image_name? + image_name : "(none)"); } -static char *make_filename_lines(const char *path, unsigned flags) +static int write_filename_items(struct para_buffer *b, const char *path, + unsigned flags) { - char *dirname, *ret; - const char *basename; + char *val; + int ret; if (!(flags & LS_FLAG_FULL_PATH)) - return make_message("%s: %s\n", - status_item_list[SI_BASENAME], path); - basename = para_basename(path), - dirname = para_dirname(path); - ret = make_message("%s: %s\n%s: %s\n%s: %s\n", - status_item_list[SI_PATH], path, - status_item_list[SI_DIRECTORY], dirname? dirname : "?", - status_item_list[SI_BASENAME], basename? basename : "?"); - free(dirname); + return WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path); + ret = WRITE_STATUS_ITEM(b, SI_PATH, "%s\n", path); + if (ret < 0) + return ret; + val = para_basename(path); + ret = WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", val? val : ""); + if (ret < 0) + return ret; + val = para_dirname(path); + ret = WRITE_STATUS_ITEM(b, SI_DIRECTORY, "%s\n", val? val : ""); + free(val); return ret; } @@ -811,6 +825,14 @@ out: return ret; } +static int write_score(struct para_buffer *b, struct ls_data *d, + struct ls_options *opts) +{ + if (!(opts->flags & LS_FLAG_ADMISSIBLE_ONLY)) /* no score*/ + return 0; + return WRITE_STATUS_ITEM(b, SI_SCORE, "%li\n", d->score); +} + static int print_list_item(struct ls_data *d, struct ls_options *opts, struct para_buffer *b, time_t current_time) { @@ -818,13 +840,9 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, char att_buf[65]; char last_played_time[30]; char duration_buf[30]; /* nobody has an audio file long enough to overflow this */ - char score_buf[30] = ""; struct afs_info *afsi = &d->afsi; struct afh_info *afhi = &d->afhi; - struct ls_widths *w = &opts->widths; - int have_score = opts->flags & LS_FLAG_ADMISSIBLE_ONLY; char asc_hash[2 * HASH_SIZE + 1]; - char *att_lines, *lyrics_lines, *image_lines, *filename_lines; if (opts->mode == LS_MODE_SHORT) { ret = para_printf(b, "%s\n", d->path); @@ -840,16 +858,15 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, if (ret < 0) goto out; get_duration_buf(afhi->seconds_total, duration_buf, opts); - if (have_score) { - if (opts->mode == LS_MODE_LONG) - sprintf(score_buf, "%*li ", w->score_width, d->score); - else - sprintf(score_buf, "%li ", d->score); - } - if (opts->mode == LS_MODE_LONG) { + struct ls_widths *w = &opts->widths; + if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) { + ret = para_printf(b, "%*li ", + opts->widths.score_width, d->score); + if (ret < 0) + goto out; + } ret = para_printf(b, - "%s" /* score */ "%s " /* attributes */ "%*u " /* amp */ "%*d " /* image_id */ @@ -862,7 +879,6 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, "%*d " /* num_played */ "%s " /* last_played */ "%s\n", /* path */ - score_buf, att_buf, w->amp_width, afsi->amp, w->image_id_width, afsi->image_id, @@ -878,13 +894,6 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, ); goto out; } - hash_to_asc(d->hash, asc_hash); - ret = make_attribute_lines(att_buf, afsi, &att_lines); - if (ret < 0) - goto out; - lyrics_lines = make_lyrics_lines(afsi); - image_lines = make_image_lines(afsi); - filename_lines = make_filename_lines(d->path, opts->flags); if (opts->mode == LS_MODE_MBOX) { const char *bn = para_basename(d->path); ret = para_printf(b, @@ -896,57 +905,78 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, if (ret < 0) goto out; } - ret = para_printf(b, - "%s" /* filename stuff */ - "%s%s%s%s" /* score */ - "%s\n" /* attributes */ - "%s: %s\n" /* hash */ - "%s" /* image id, image name */ - "%s" /* lyrics */ - "%s: %dkbit/s\n" /* bitrate */ - "%s: %s\n" /* format */ - "%s: %dHz\n" /* frequency */ - "%s: %d\n" /* channels */ - "%s: %s\n" /* duration */ - "%s: %lu\n" /* seconds total */ - "%s: %s\n" /* last played time */ - "%s: %d\n" /* num_played */ - "%s: %u\n" /* amplification */ - "%s: %lu\n" /* chunk time */ - "%s: %lu\n" /* num chunks */ - "%s: %s\n" /* techinfo */ - "%s: %s\n" /* artist */ - "%s: %s\n" /* title */ - "%s: %s\n" /* year */ - "%s: %s\n" /* album */ - "%s: %s\n", /* comment */ - filename_lines, - have_score? status_item_list[SI_SCORE] : "", - have_score? ": " : "", - score_buf, - have_score? "\n" : "", - att_lines, - status_item_list[SI_HASH], asc_hash, - image_lines, - lyrics_lines, - status_item_list[SI_BITRATE], afhi->bitrate, - status_item_list[SI_FORMAT], audio_format_name(afsi->audio_format_id), - status_item_list[SI_FREQUENCY], afhi->frequency, - status_item_list[SI_CHANNELS], afhi->channels, - status_item_list[SI_DURATION], duration_buf, - status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total, - status_item_list[SI_LAST_PLAYED], last_played_time, - status_item_list[SI_NUM_PLAYED], afsi->num_played, - status_item_list[SI_AMPLIFICATION], afsi->amp, - status_item_list[SI_CHUNK_TIME], tv2ms(&afhi->chunk_tv), - status_item_list[SI_NUM_CHUNKS], afhi->chunks_total, - status_item_list[SI_TECHINFO], afhi->techinfo, - status_item_list[SI_ARTIST], afhi->tags.artist, - status_item_list[SI_TITLE], afhi->tags.title, - status_item_list[SI_YEAR], afhi->tags.year, - status_item_list[SI_ALBUM], afhi->tags.album, - status_item_list[SI_COMMENT], afhi->tags.comment - ); + ret = write_filename_items(b, d->path, opts->flags); + if (ret < 0) + goto out; + ret = write_score(b, d, opts); + if (ret < 0) + goto out; + ret = write_attribute_items(b, att_buf, afsi); + if (ret < 0) + goto out; + ret = write_image_items(b, afsi); + if (ret < 0) + goto out; + ret = write_lyrics_items(b, afsi); + if (ret < 0) + goto out; + hash_to_asc(d->hash, asc_hash); + ret = WRITE_STATUS_ITEM(b, SI_HASH, "%s\n", asc_hash); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_BITRATE, "%dkbit/s\n", afhi->bitrate); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_FORMAT, "%s\n", + audio_format_name(afsi->audio_format_id)); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_FREQUENCY, "%dHz\n", afhi->frequency); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_CHANNELS, "%d\n", afhi->channels); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_DURATION, "%s\n", duration_buf); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_SECONDS_TOTAL, "%lu\n", + afhi->seconds_total); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_LAST_PLAYED, "%s\n", last_played_time); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_NUM_PLAYED, "%d\n", afsi->num_played); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_AMPLIFICATION, "%u\n", afsi->amp); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n", + tv2ms(&afhi->chunk_tv)); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%lu\n", + afhi->chunks_total); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_YEAR, "%s\n", afhi->tags.year); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_ALBUM, "%s\n", afhi->tags.album); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_COMMENT, "%s\n", afhi->tags.comment); if (ret < 0) goto out; if (opts->mode == LS_MODE_MBOX) { @@ -958,10 +988,6 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, osl_close_disk_object(&lyrics_def); } } - free(att_lines); - free(lyrics_lines); - free(image_lines); - free(filename_lines); out: return ret; } @@ -989,8 +1015,19 @@ static int make_status_items(struct audio_file_data *afd, ret = print_list_item(&d, &opts, &pb, current_time); if (ret < 0) return ret; - free(current_status_items); - current_status_items = pb.buf; + free(status_items); + status_items = pb.buf; + memset(&pb, 0, sizeof(pb)); + pb.max_size = SHMMAX - 1; + pb.flags = PBF_SIZE_PREFIX; + ret = print_list_item(&d, &opts, &pb, current_time); + if (ret < 0) { + free(status_items); + status_items = NULL; + return ret; + } + free(parser_friendly_status_items); + parser_friendly_status_items = pb.buf; return 1; } @@ -1273,11 +1310,11 @@ static void com_ls_callback(int fd, const struct osl_object *query) struct ls_options *opts = query->data; char *p, *pattern_start = (char *)query->data + sizeof(*opts); struct para_buffer b = {.max_size = SHMMAX, + .flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0, .max_size_handler = pass_buffer_as_shm, .private_data = &fd}; int i = 0, ret; time_t current_time; - if (opts->num_patterns) { opts->patterns = para_malloc(opts->num_patterns * sizeof(char *)); for (i = 0, p = pattern_start; i < opts->num_patterns; i++) { @@ -1363,6 +1400,9 @@ int com_ls(int fd, int argc, char * const * const argv) case 'c': mode = LS_MODE_CHUNKS; continue; + case 'p': + mode = LS_MODE_PARSER; + continue; default: return -E_AFT_SYNTAX; } @@ -2369,7 +2409,7 @@ int com_cpsi(int fd, int argc, char * const * const argv) } break; } - if (i + 1 >= argc) /* need at least souce file and pattern */ + if (i + 1 >= argc) /* need at least source file and pattern */ return -E_AFT_SYNTAX; if (!(flags & ~CPSI_FLAG_VERBOSE)) /* no copy flags given */ flags = ~(unsigned)CPSI_FLAG_VERBOSE | flags; @@ -2380,16 +2420,23 @@ int com_cpsi(int fd, int argc, char * const * const argv) return ret; } -void afs_stat_callback(int fd, __a_unused const struct osl_object *query) +void afs_stat_callback(int fd, const struct osl_object *query) { - if (current_status_items) - pass_buffer_as_shm(current_status_items, - strlen(current_status_items), &fd); + int *parser_friendly = query->data; + char *buf = *parser_friendly? + parser_friendly_status_items : status_items; + + if (!buf) + return; + pass_buffer_as_shm(buf, strlen(buf), &fd); } -int send_afs_status(int fd) +int send_afs_status(int fd, int parser_friendly) { - return send_callback_request(afs_stat_callback, NULL, send_result, &fd); + struct osl_object query = {.data = &parser_friendly, + .size = sizeof(parser_friendly)}; + + return send_callback_request(afs_stat_callback, &query, send_result, &fd); } /* TODO: optionally fix problems by removing offending rows */ @@ -2470,8 +2517,10 @@ static void aft_close(void) { osl_close_table(audio_file_table, OSL_MARK_CLEAN); audio_file_table = NULL; - free(current_status_items); - current_status_items = NULL; + free(status_items); + status_items = NULL; + free(parser_friendly_status_items); + parser_friendly_status_items = NULL; } /** diff --git a/amp_filter.c b/amp_filter.c index a011c411..a62344a1 100644 --- a/amp_filter.c +++ b/amp_filter.c @@ -76,11 +76,9 @@ static void amp_open(struct filter_node *fn) pad->conf = fn->conf; fn->private_data = pad; - if (!pad->conf->amp_given && stat_item_values[SI_AMPLIFICATION]) { - int i = SI_AMPLIFICATION; - char *s = stat_item_values[i] + strlen(status_item_list[i]) + 1; - sscanf(s, "%u", &pad->amp); - } else + if (!pad->conf->amp_given && stat_item_values[SI_AMPLIFICATION]) + sscanf(stat_item_values[SI_AMPLIFICATION], "%u", &pad->amp); + else pad->amp = pad->conf->amp_arg; fn->bufsize = AMP_CHUNK_SIZE; fn->buf = para_malloc(fn->bufsize); diff --git a/audiod.c b/audiod.c index eb3b200a..767afa8d 100644 --- a/audiod.c +++ b/audiod.c @@ -219,8 +219,7 @@ out: seconds = PARA_MIN(seconds, length); seconds = PARA_MAX(seconds, 0); return make_message( - "%s: %s%d:%02d [%d:%02d] (%d%%/%d:%02d)\n", - status_item_list[SI_PLAY_TIME], + "%s%d:%02d [%d:%02d] (%d%%/%d:%02d)", s? "" : "~", seconds / 60, seconds % 60, @@ -231,7 +230,7 @@ out: length % 60 ); empty: - return make_message("%s:\n", status_item_list[SI_PLAY_TIME]); + return para_strdup(NULL); } static int want_colors(void) @@ -578,45 +577,31 @@ out: return count; } -static int check_stat_line(char *line, __a_unused void *data) +static int update_item(int itemnum, char *buf) { - int itemnum; - size_t ilen = 0; long unsigned sec, usec; - char *tmp; - //PARA_INFO_LOG("line: %s\n", line); - if (!line) - return 1; - itemnum = stat_line_valid(line); - if (itemnum < 0) { - PARA_WARNING_LOG("invalid status line: %s\n", line); - return 1; - } if (stat_task->clock_diff_count && itemnum != SI_CURRENT_TIME) return 1; - tmp = make_message("%s\n", line); - stat_client_write(tmp, itemnum); - free(tmp); free(stat_item_values[itemnum]); - stat_item_values[itemnum] = para_strdup(line); - ilen = strlen(status_item_list[itemnum]); + stat_item_values[itemnum] = para_strdup(buf); + stat_client_write_item(itemnum); switch (itemnum) { case SI_STATUS_FLAGS: stat_task->vss_status = 0; - if (strchr(line, 'N')) + if (strchr(buf, 'N')) stat_task->vss_status |= VSS_STATUS_FLAG_NEXT; - if (strchr(line, 'P')) + if (strchr(buf, 'P')) stat_task->vss_status |= VSS_STATUS_FLAG_PLAYING; break; case SI_OFFSET: - stat_task->offset_seconds = atoi(line + ilen + 1); + stat_task->offset_seconds = atoi(buf); break; case SI_SECONDS_TOTAL: - stat_task->length_seconds = atoi(line + ilen + 1); + stat_task->length_seconds = atoi(buf); break; case SI_STREAM_START: - if (sscanf(line + ilen + 1, "%lu.%lu", &sec, &usec) == 2) { + if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) { struct timeval a_start, delay; delay.tv_sec = conf.stream_delay_arg / 1000; delay.tv_usec = (conf.stream_delay_arg % 1000) * 1000; @@ -634,14 +619,14 @@ static int check_stat_line(char *line, __a_unused void *data) } break; case SI_CURRENT_TIME: - if (sscanf(line + ilen + 1, "%lu.%lu", &sec, &usec) == 2) { + if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) { struct timeval tv = {sec, usec}; compute_time_diff(&tv); } break; case SI_FORMAT: - stat_task->current_audio_format_num = get_audio_format_num( - line + ilen + 1); + stat_task->current_audio_format_num + = get_audio_format_num(buf); } return 1; } @@ -935,17 +920,11 @@ static void init_command_task(struct command_task *ct) static void close_stat_pipe(void) { - int i; - if (!stat_task->ct) return; client_close(stat_task->ct); stat_task->ct = NULL; - FOR_EACH_STATUS_ITEM(i) { - free(stat_item_values[i]); - stat_item_values[i] = NULL; - } - dump_empty_status(); + clear_and_dump_items(); stat_task->length_seconds = 0; stat_task->offset_seconds = 0; stat_task->vss_status = 0; @@ -1069,7 +1048,7 @@ static void status_pre_select(struct sched *s, struct task *t) goto out; } if (st->ct) { - unsigned bytes_left; + int ret; if (st->ct->task.error < 0) { if (st->ct->task.error != -E_TASK_UNREGISTERED) goto out; @@ -1078,11 +1057,15 @@ static void status_pre_select(struct sched *s, struct task *t) } if (st->ct->status != CL_RECEIVING) goto out; - bytes_left = for_each_line(st->ct->buf, st->ct->loaded, - &check_stat_line, NULL); - if (st->ct->loaded != bytes_left) { + ret = for_each_stat_item(st->ct->buf, st->ct->loaded, + update_item); + if (ret < 0) { + st->ct->task.error = ret; + goto out; + } + if (st->ct->loaded != ret) { st->last_status_read = *now; - st->ct->loaded = bytes_left; + st->ct->loaded = ret; } else { struct timeval diff; tv_diff(now, &st->last_status_read, &diff); @@ -1094,25 +1077,23 @@ static void status_pre_select(struct sched *s, struct task *t) if (tv_diff(now, &st->restart_barrier, NULL) < 0) goto out; if (st->clock_diff_count) { /* get status only one time */ - char *argv[] = {"audiod", "stat", "1", NULL}; - int argc = 3; + char *argv[] = {"audiod", "--", "stat", "-p", "1", NULL}; + int argc = 5; PARA_INFO_LOG("clock diff count: %d\n", st->clock_diff_count); st->clock_diff_count--; client_open(argc, argv, &st->ct, NULL); set_stat_task_restart_barrier(2); } else { - char *argv[] = {"audiod", "stat", NULL}; - int argc = 2; + char *argv[] = {"audiod", "--", "stat", "-p", NULL}; + int argc = 4; client_open(argc, argv, &st->ct, NULL); set_stat_task_restart_barrier(5); } free(stat_item_values[SI_BASENAME]); - stat_item_values[SI_BASENAME] = make_message( - "%s: no connection to para_server\n", - status_item_list[SI_BASENAME]); - stat_client_write(stat_item_values[SI_BASENAME], - SI_BASENAME); + stat_item_values[SI_BASENAME] = para_strdup( + "no connection to para_server"); + stat_client_write_item(SI_BASENAME); st->last_status_read = *now; out: start_stop_decoders(s); diff --git a/audiod.cmd b/audiod.cmd index 5469476c..2c6ea9b1 100644 --- a/audiod.cmd +++ b/audiod.cmd @@ -51,8 +51,9 @@ H: Stop all decoders but leave connection to para_server open. --- N: stat D: print status information -U: stat [item1 ...] -H: Dump given status items (all if none given) to stdout. +U: stat [-p] [item1 ...] +H: Dump given status items (all if none given) to stdout. If -p is given, use +H: parser-friendly mode. --- N: tasks D: list current tasks diff --git a/audiod.h b/audiod.h index 8b416ce5..6946fc78 100644 --- a/audiod.h +++ b/audiod.h @@ -82,7 +82,6 @@ extern int audiod_status; void __noreturn clean_exit(int status, const char *msg); int handle_connect(int accept_fd); void audiod_status_dump(void); -void dump_empty_status(void); char *get_time_string(int slot_num); /** iterate over all slots */ diff --git a/audiod_command.c b/audiod_command.c index 3467dbe1..98e83b3c 100644 --- a/audiod_command.c +++ b/audiod_command.c @@ -35,7 +35,11 @@ extern char *stat_item_values[NUM_STAT_ITEMS]; static int client_write(int fd, const char *buf) { - size_t len = strlen(buf); + size_t len; + + if (!buf) + return 0; + len = strlen(buf); return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1; } @@ -43,7 +47,7 @@ __malloc static char *audiod_status_string(void) { const char *status = (audiod_status == AUDIOD_ON)? "on" : (audiod_status == AUDIOD_OFF)? "off": "sb"; - return make_message("%s: %s\n", status_item_list[SI_AUDIOD_STATUS], status); + return para_strdup(status); } static int get_play_time_slot_num(void) @@ -72,13 +76,14 @@ __malloc static char *decoder_flags(void) char flag = '0'; if (s->receiver_node) flag += 1; - if (s->wng) + if (s->fc) flag += 2; + if (s->wng) + flag += 4; flags[i] = flag; } flags[MAX_STREAM_SLOTS] = '\0'; - return make_message("%s: %s\n", status_item_list[SI_DECODER_FLAGS], - flags); + return para_strdup(flags); } static int dump_commands(int fd) @@ -166,71 +171,46 @@ int com_kill(int fd, int argc, char **argv) return ret; } -int com_stat(int fd, __a_unused int argc, __a_unused char **argv) +int com_stat(int fd, int argc, char **argv) { - int i, ret; - char *buf = NULL; - long unsigned mask = ~0LU; - - if (argc > 1) { - mask = 0; - for (i = 1; i < argc; i++) { - ret = stat_item_valid(argv[i]); - if (ret < 0) - return ret; - mask |= (1 << ret); + int i, ret, parser_friendly = 0; + uint64_t mask = 0; + const uint64_t one = 1; + struct para_buffer b = {.flags = 0}; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; } - } - PARA_INFO_LOG("mask: 0x%lx\n", mask); - if (mask & (1 << SI_PLAY_TIME)) { - int slot_num = get_play_time_slot_num(); - char *ts = get_time_string(slot_num); - if (ts) { - ret = client_write(fd, ts); - if (ret < 0) - goto out; - free(ts); + if (!strncmp(arg, "-p", 2)) { + parser_friendly = 1; + b.flags = PBF_SIZE_PREFIX; + continue; } } - if (mask & (1 << SI_AUDIOD_UPTIME)) { - char *tmp, *us = uptime_str(); - tmp = make_message("%s: %s\n", - status_item_list[SI_AUDIOD_UPTIME], us); - free(us); - ret = client_write(fd, tmp); + if (i >= argc) + mask--; /* set all bits */ + for (; i < argc; i++) { + ret = stat_item_valid(argv[i]); if (ret < 0) - goto out; - free(tmp); - } - if (mask & (1 << SI_AUDIOD_STATUS)) { - char *s = audiod_status_string(); - ret = client_write(fd, s); - if (ret < 0) - goto out; - free(s); - } - if (mask & (1 << SI_DECODER_FLAGS)) { - char *df = decoder_flags(); - ret = client_write(fd, df); - if (ret < 0) - goto out; - free(df); + return ret; + mask |= (one << ret); } + PARA_INFO_LOG("mask: 0x%llx\n", (long long unsigned)mask); FOR_EACH_STATUS_ITEM(i) { - char *tmp, *v; - if (!((1 << i) & mask)) + char *item = stat_item_values[i]; + if (!((one << i) & mask)) continue; - v = stat_item_values[i]; - tmp = make_message("%s%s%s", buf? buf: "", - v? v : "", v? "\n" : ""); - free(buf); - buf = tmp; + WRITE_STATUS_ITEM(&b, i, "%s\n", item? item : ""); } - ret = client_write(fd, buf); -out: - if (ret > 0) - ret = stat_client_add(fd, mask); - free(buf); + ret = client_write(fd, b.buf); + if (ret >= 0) + ret = stat_client_add(fd, mask, parser_friendly); + free(b.buf); return ret; } @@ -420,20 +400,20 @@ out: return ret; } /** - * send the current audiod status to all connected stat clients + * Send the current audiod status to all connected stat clients. */ void audiod_status_dump(void) { int slot_num = get_play_time_slot_num(); - char *old, *new, *tmp; + char *old, *new; old = stat_item_values[SI_PLAY_TIME]; new = get_time_string(slot_num); if (new) { if (!old || strcmp(old, new)) { free(old); - stat_client_write(new, SI_PLAY_TIME); stat_item_values[SI_PLAY_TIME] = new; + stat_client_write_item(SI_PLAY_TIME); } else free(new); } @@ -442,11 +422,8 @@ void audiod_status_dump(void) old = stat_item_values[SI_AUDIOD_UPTIME]; if (!old || strcmp(old, new)) { free(old); - tmp = make_message("%s: %s\n", - status_item_list[SI_AUDIOD_UPTIME], new); - stat_client_write(tmp, SI_AUDIOD_UPTIME); - free(tmp); stat_item_values[SI_AUDIOD_UPTIME] = new; + stat_client_write_item(SI_AUDIOD_UPTIME); } else free(new); @@ -454,8 +431,8 @@ void audiod_status_dump(void) new = audiod_status_string(); if (!old || strcmp(old, new)) { free(old); - stat_client_write(new, SI_AUDIOD_STATUS); stat_item_values[SI_AUDIOD_STATUS] = new; + stat_client_write_item(SI_AUDIOD_STATUS); } else free(new); @@ -463,27 +440,25 @@ void audiod_status_dump(void) new = decoder_flags(); if (!old || strcmp(old, new)) { free(old); - stat_client_write(new, SI_DECODER_FLAGS); stat_item_values[SI_DECODER_FLAGS] = new; + stat_client_write_item(SI_DECODER_FLAGS); } else free(new); } /** - * send empty status list + * Flush and send all status items. * * Send to each connected client the full status item list * with empty values. */ -void dump_empty_status(void) +void clear_and_dump_items(void) { int i; FOR_EACH_STATUS_ITEM(i) { - char *tmp = make_message("%s:\n", status_item_list[i]); - stat_client_write(tmp, i); - free(tmp); free(stat_item_values[i]); stat_item_values[i] = NULL; + stat_client_write_item(i); } } diff --git a/command.c b/command.c index e6a16e53..17132e87 100644 --- a/command.c +++ b/command.c @@ -103,14 +103,15 @@ static char *vss_get_status_flags(unsigned int flags) return msg; } -static char *get_status(struct misc_meta_data *nmmd) +static char *get_status(struct misc_meta_data *nmmd, int parser_friendly) { - char *ret, mtime[30] = ""; + char mtime[30] = ""; char *status, *flags; /* vss status info */ char *ut = uptime_str(); long offset = (nmmd->offset + 500) / 1000; struct timeval current_time; struct tm mtime_tm; + struct para_buffer b = {.flags = parser_friendly? PBF_SIZE_PREFIX : 0}; /* report real status */ status = vss_status_tohuman(nmmd->vss_status_flags); @@ -120,34 +121,22 @@ static char *get_status(struct misc_meta_data *nmmd) strftime(mtime, 29, "%b %d %Y", &mtime_tm); } gettimeofday(¤t_time, NULL); - ret = make_message( - "%s: %zu\n" /* file size */ - "%s: %s\n" /* mtime */ - "%s: %s\n" /* status */ - "%s: %s\n" /* status flags */ - "%s: %li\n" /* offset */ - "%s: %s\n" /* afs mode */ - "%s: %lu.%lu\n" /* stream start */ - "%s: %lu.%lu\n", /* current server time */ - status_item_list[SI_FILE_SIZE], nmmd->size / 1024, - status_item_list[SI_MTIME], mtime, - status_item_list[SI_STATUS], status, - status_item_list[SI_STATUS_FLAGS], flags, - - status_item_list[SI_OFFSET], offset, - status_item_list[SI_AFS_MODE], mmd->afs_mode_string, - - status_item_list[SI_STREAM_START], - (long unsigned)nmmd->stream_start.tv_sec, - (long unsigned)nmmd->stream_start.tv_usec, - status_item_list[SI_CURRENT_TIME], - (long unsigned)current_time.tv_sec, - (long unsigned)current_time.tv_usec - ); + WRITE_STATUS_ITEM(&b, SI_FILE_SIZE, "%zu\n", nmmd->size / 1024); + WRITE_STATUS_ITEM(&b, SI_MTIME, "%s\n", mtime); + WRITE_STATUS_ITEM(&b, SI_STATUS, "%s\n", status); + WRITE_STATUS_ITEM(&b, SI_STATUS_FLAGS, "%s\n", flags); + WRITE_STATUS_ITEM(&b, SI_OFFSET, "%li\n", offset); + WRITE_STATUS_ITEM(&b, SI_AFS_MODE, "%s\n", mmd->afs_mode_string); + WRITE_STATUS_ITEM(&b, SI_STREAM_START, "%lu.%lu\n", + (long unsigned)nmmd->stream_start.tv_sec, + (long unsigned)nmmd->stream_start.tv_usec); + WRITE_STATUS_ITEM(&b, SI_CURRENT_TIME, "%lu.%lu\n", + (long unsigned)current_time.tv_sec, + (long unsigned)current_time.tv_usec); free(flags); free(status); free(ut); - return ret; + return b.buf; } static int check_sender_args(int argc, char * const * argv, struct sender_command_data *scd) @@ -301,87 +290,96 @@ int com_version(int fd, int argc, __a_unused char * const * argv) ); } +#define EMPTY_STATUS_ITEMS \ + ITEM(PATH) \ + ITEM(DIRECTORY) \ + ITEM(BASENAME) \ + ITEM(SCORE) \ + ITEM(ATTRIBUTES_BITMAP) \ + ITEM(ATTRIBUTES_TXT) \ + ITEM(HASH) \ + ITEM(IMAGE_ID) \ + ITEM(IMAGE_NAME) \ + ITEM(LYRICS_ID) \ + ITEM(LYRICS_NAME) \ + ITEM(BITRATE) \ + ITEM(FORMAT) \ + ITEM(FREQUENCY) \ + ITEM(CHANNELS) \ + ITEM(DURATION) \ + ITEM(SECONDS_TOTAL) \ + ITEM(NUM_PLAYED) \ + ITEM(LAST_PLAYED) \ + ITEM(TECHINFO) \ + ITEM(ARTIST) \ + ITEM(TITLE) \ + ITEM(YEAR) \ + ITEM(ALBUM) \ + ITEM(COMMENT) \ + ITEM(AMPLIFICATION) + /** * Write a list of audio-file related status items with empty values. * * This is used by vss when currently no audio file is open. */ -static char *empty_status_items(void) -{ +static char *empty_status_items(int parser_friendly) +{ + if (parser_friendly) + return make_message( + #define ITEM(x) "0004 %02x:\n" + EMPTY_STATUS_ITEMS + #undef ITEM + #define ITEM(x) , SI_ ## x + EMPTY_STATUS_ITEMS + #undef ITEM + ); return make_message( - "%s: \n" /* path */ - "%s: \n" /* dirname */ - "%s: \n" /* basename */ - "%s: \n" /* score */ - "%s: \n" /* attributes bitmap */ - "%s: \n" /* attributes txt */ - "%s: \n" /* hash */ - "%s: \n" /* image id */ - "%s: \n" /* image name */ - "%s: \n" /* lyrics id */ - "%s: \n" /* lyrics name */ - "%s: \n" /* bitrate */ - "%s: \n" /* format */ - "%s: \n" /* frequency */ - "%s: \n" /* channels */ - "%s: \n" /* duration */ - "%s: \n" /* seconds total */ - "%s: \n" /* num played */ - "%s: \n" /* last played */ - "%s: \n" /* techinfo */ - "%s: \n" /* artist */ - "%s: \n" /* title */ - "%s: \n" /* year */ - "%s: \n" /* album */ - "%s: \n" /* comment */ - "%s: \n" /* amplification */ - , - status_item_list[SI_PATH], - status_item_list[SI_DIRECTORY], - status_item_list[SI_BASENAME], - status_item_list[SI_SCORE], - status_item_list[SI_ATTRIBUTES_BITMAP], - status_item_list[SI_ATTRIBUTES_TXT], - status_item_list[SI_HASH], - status_item_list[SI_IMAGE_ID], - status_item_list[SI_IMAGE_NAME], - status_item_list[SI_LYRICS_ID], - status_item_list[SI_LYRICS_NAME], - status_item_list[SI_BITRATE], - status_item_list[SI_FORMAT], - status_item_list[SI_FREQUENCY], - status_item_list[SI_CHANNELS], - status_item_list[SI_DURATION], - status_item_list[SI_SECONDS_TOTAL], - status_item_list[SI_NUM_PLAYED], - status_item_list[SI_LAST_PLAYED], - status_item_list[SI_TECHINFO], - status_item_list[SI_ARTIST], - status_item_list[SI_TITLE], - status_item_list[SI_YEAR], - status_item_list[SI_ALBUM], - status_item_list[SI_COMMENT], - status_item_list[SI_AMPLIFICATION] + #define ITEM(x) "%s:\n" + EMPTY_STATUS_ITEMS + #undef ITEM + #define ITEM(x) ,status_item_list[SI_ ## x] + EMPTY_STATUS_ITEMS + #undef ITEM ); } +#undef EMPTY_STATUS_ITEMS /* stat */ int com_stat(int fd, int argc, char * const * argv) { - int ret, num = 0;/* status will be printed that many - * times. num <= 0 means: print forever - */ + int i, ret; struct misc_meta_data tmp, *nmmd = &tmp; char *s; + int32_t num = 0; + int parser_friendly = 0; para_sigaction(SIGUSR1, dummy); - if (argc > 1) - num = atoi(argv[1]); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strncmp(arg, "-n=", 3)) { + ret = para_atoi32(arg + 3, &num); + if (ret < 0) + return ret; + continue; + } + if (!strcmp(arg, "-p")) { + parser_friendly = 1; + continue; + } + } + if (i != argc) + return -E_COMMAND_SYNTAX; for (;;) { - mmd_dup(nmmd); - s = get_status(nmmd); + s = get_status(nmmd, parser_friendly); ret = send_buffer(fd, s); free(s); if (ret < 0) @@ -389,12 +387,12 @@ int com_stat(int fd, int argc, char * const * argv) if (nmmd->vss_status_flags & VSS_NEXT) { static char *esi; if (!esi) - esi = empty_status_items(); + esi = empty_status_items(parser_friendly); ret = send_buffer(fd, esi); if (ret < 0) goto out; } else - send_afs_status(fd); + send_afs_status(fd, parser_friendly); ret = 1; if (num > 0 && !--num) goto out; diff --git a/compress_filter.c b/compress_filter.c index f853b633..65bc8e51 100644 --- a/compress_filter.c +++ b/compress_filter.c @@ -22,8 +22,6 @@ /** The size of the output data buffer. */ #define COMPRESS_CHUNK_SIZE 40960 -extern char *stat_item_values[NUM_STAT_ITEMS]; - /** Data specific to the compress filter. */ struct private_compress_data { /** The current multiplier. */ diff --git a/error.h b/error.h index 3a601ed1..a06baf21 100644 --- a/error.h +++ b/error.h @@ -238,6 +238,7 @@ extern const char **para_errlist[]; #define STAT_ERRORS \ PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \ PARA_ERROR(UNKNOWN_STAT_ITEM, "status item not recognized"), \ + PARA_ERROR(STAT_ITEM_PARSE, "failed to parse status item"), \ #define OGGDEC_FILTER_ERRORS \ @@ -276,6 +277,7 @@ extern const char **para_errlist[]; PARA_ERROR(STRTOLL, "unknown strtoll error"), \ PARA_ERROR(ATOI_NO_DIGITS, "no digits found in string"), \ PARA_ERROR(ATOI_JUNK_AT_END, "further characters after number"), \ + PARA_ERROR(SIZE_PREFIX, "bad size prefix") \ #define EXEC_ERRORS \ diff --git a/gui.c b/gui.c index fc2cd9a1..6806cc8f 100644 --- a/gui.c +++ b/gui.c @@ -203,24 +203,6 @@ static int para_open_audiod_pipe(char *cmd) return ret; } -static int read_audiod_pipe(int fd, line_handler_t *line_handler) -{ - static char buf[4096]; - const ssize_t bufsize = sizeof(buf) - 1; - static ssize_t loaded; - ssize_t ret; - - if (loaded >= bufsize) - loaded = 0; - ret = read(fd, buf + loaded, bufsize - loaded); - if (ret > 0) { - loaded += ret; - buf[loaded] = '\0'; - loaded = for_each_line(buf, loaded, line_handler, NULL); - } - return ret; -} - static int find_cmd_byname(char *name) { int i; @@ -687,6 +669,36 @@ static void print_stat_item(int i) wrefresh(top.win); } +/* + * print status line if line starts with known command. + */ +static int update_item(int item_num, char *buf) +{ + free(stat_content[item_num]); + stat_content[item_num] = para_strdup(buf); + print_stat_item(item_num); + return 1; +} + +static int read_audiod_pipe(int fd) +{ + static char buf[8192]; + static int loaded; + int ret; + + if (loaded >= sizeof(buf)) /* overflow */ + return 0; + ret = read(fd, buf + loaded, sizeof(buf) - loaded); + if (ret <= 0) + return ret; + loaded += ret; + ret = for_each_stat_item(buf, loaded, update_item); + if (ret < 0) + return ret; + loaded = ret; + return 1; +} + static void print_all_items(void) { int i; @@ -769,26 +781,6 @@ reap_next_child: goto reap_next_child; } -/* - * print status line if line starts with known command. - */ -static int check_stat_line(char *line, __a_unused void *data) -{ - int i; - -// PARA_INFO_LOG("%s: checking: %s\n", __func__, line); - i = stat_line_valid(line); - if (i >= 0) { - line += strlen(status_item_list[i]) + 1; - if (*line == ' ') - line++; - free(stat_content[i]); - stat_content[i] = para_strdup(line); - print_stat_item(i); - } - return 1; -} - /* * This sucker modifies its first argument. *handler and *arg are * pointers to 0-terminated strings (inside line). Crap. @@ -859,8 +851,8 @@ static void handle_signal(int sig) return; case SIGINT: PARA_WARNING_LOG("caught SIGINT, reset"); - /* Nothing to do. SIGINT killed our child, para_client stat. - * This get noticed by do_select which resets everything + /* Nothing to do. SIGINT killed our child which gets noticed + * by do_select and resets everything. */ return; case SIGUSR1: @@ -946,7 +938,7 @@ repeat: } } if (audiod_pipe >= 0 && FD_ISSET(audiod_pipe, &rfds)) - if (read_audiod_pipe(audiod_pipe, check_stat_line) <= 0) { + if (read_audiod_pipe(audiod_pipe) <= 0) { close(audiod_pipe); audiod_pipe = -1; clear_all_items(); diff --git a/para.h b/para.h index 7cdc5e07..995bfb9d 100644 --- a/para.h +++ b/para.h @@ -181,9 +181,11 @@ extern const char *status_item_list[]; /** Loop over each status item. */ #define FOR_EACH_STATUS_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++) int stat_item_valid(const char *item); -int stat_line_valid(const char *); -void stat_client_write(const char *msg, int itemnum); -int stat_client_add(int fd, long unsigned mask); +void stat_client_write_item(int item_num); +int stat_client_add(int fd, uint64_t mask, int parser_friendly); +int for_each_stat_item(char *item_buf, size_t num_bytes, + int (*item_handler)(int, char *)); +void clear_and_dump_items(void); __printf_2_3 void para_log(int, const char*, ...); diff --git a/server.cmd b/server.cmd index 847d53d7..a089060a 100644 --- a/server.cmd +++ b/server.cmd @@ -93,11 +93,14 @@ H: Print server uptime and other information. --- N: stat P: VSS_READ -D: Print status info for current audio file. -U: stat [n] -H: Without any arguments, stat continuously prints status messages -H: about the audio file being streamed. Use the optional number n -H: to let stat exit after having displayed status n times. +D: Print status info for the current audio file. +U: stat [-n num] [-p] +H: If -n is given, the command exits after having displayed the status n +H: times. Otherwise, the command runs in an endless loop. +H: +H: The -p option activates parser-friendly output: Each status item is +H: prefixed with its size in bytes and the status items identifiers are +H: printed as numerical values. --- N: stop P: VSS_READ | VSS_WRITE diff --git a/stat.c b/stat.c index 4ba1b0e8..1062da36 100644 --- a/stat.c +++ b/stat.c @@ -23,6 +23,14 @@ /** The maximal number of simultaneous connections. */ #define MAX_STAT_CLIENTS 50 +extern char *stat_item_values[NUM_STAT_ITEMS]; + +/** Flags used for the stat command of para_audiod. */ +enum stat_client_flags { + /** Enable parser-friendly output. */ + SCF_PARSER_FRIENDLY = 1, +}; + /** * Describes a status client of para_audiod. * @@ -35,7 +43,9 @@ struct stat_client { /** The stat client's file descriptor. */ int fd; /** Bitmask of those status items the client is interested in. */ - long unsigned item_mask; + uint64_t item_mask; + /** See \ref stat_client flags. s*/ + unsigned flags; /** Its entry in the list of stat clients. */ struct list_head node; }; @@ -61,6 +71,7 @@ static void dump_stat_client_list(void) * * \param fd The file descriptor of the client. * \param mask Bitfield of status items for this client. + * \param parser_friendly Enable parser-friendly output mode. * * Only those status items having the bit set in \a mask will be * sent to the client. @@ -68,7 +79,7 @@ static void dump_stat_client_list(void) * \return Positive value on success, or -E_TOO_MANY_CLIENTS if * the number of connected clients exceeds #MAX_STAT_CLIENTS. */ -int stat_client_add(int fd, long unsigned mask) +int stat_client_add(int fd, uint64_t mask, int parser_friendly) { struct stat_client *new_client; @@ -82,9 +93,11 @@ int stat_client_add(int fd, long unsigned mask) initialized = 1; } PARA_INFO_LOG("adding client on fd %d\n", fd); - new_client = para_malloc(sizeof(struct stat_client)); + new_client = para_calloc(sizeof(struct stat_client)); new_client->fd = fd; new_client->item_mask = mask; + if (parser_friendly) + new_client->flags = SCF_PARSER_FRIENDLY; para_list_add(&new_client->node, &client_list); dump_stat_client_list(); num_clients++; @@ -93,28 +106,34 @@ int stat_client_add(int fd, long unsigned mask) /** * Write a message to all connected status clients. * - * \param msg A \p NULL terminated buffer. - * \param itemnum The number of the status item of \a msg. + * \param item_num The number of the status item of \a msg. * * On write errors, remove the status client from the client list and close its * file descriptor. */ -void stat_client_write(const char *msg, int itemnum) +void stat_client_write_item(int item_num) { struct stat_client *sc, *tmp; - size_t len = strlen(msg); + struct para_buffer pb = {.flags = 0}; + struct para_buffer pfpb = {.flags = PBF_SIZE_PREFIX}; + const uint64_t one = 1; - if (!initialized || !len) + if (!initialized) return; list_for_each_entry_safe(sc, tmp, &client_list, node) { int fd = sc->fd, ret; - if (!((1 << itemnum) & sc->item_mask)) + if (!((one << item_num) & sc->item_mask)) continue; if (write_ok(fd) > 0) { - ret = write(fd, msg, len); - // PARA_DEBUG_LOG("dumped %s to fd %d, ret = %d\n", msg, fd, ret); - if (ret == len) + struct para_buffer *b = + (sc->flags & SCF_PARSER_FRIENDLY)? &pfpb : &pb; + char *msg = stat_item_values[item_num]; + if (!b->buf) + WRITE_STATUS_ITEM(b, item_num, "%s\n", + msg? msg : ""); + ret = write(fd, b->buf, b->offset); + if (ret == b->offset) continue; } /* write error or fd not ready for writing */ @@ -125,6 +144,8 @@ void stat_client_write(const char *msg, int itemnum) free(sc); dump_stat_client_list(); } + free(pb.buf); + free(pfpb.buf); // if (num_clients) // PARA_DEBUG_LOG("%d client(s)\n", num_clients); } @@ -151,31 +172,68 @@ int stat_item_valid(const char *item) return -E_UNKNOWN_STAT_ITEM; } +/** The minimal length of a status item buffer. */ +#define MIN_STAT_ITEM_LEN 9 /* 5 + 2 + 2, e.g. '0005 00:\n' */ + /** - * Check if line starts with known status item. + * Call a function for each complete status item of a buffer. * - * \param line Buffer containing the line. + * \param item_buf The source buffer. + * \param num_bytes The length of \a buf. + * \param item_handler Function to call for each complete item. * - * \return If the beginning of \a line matches any paraslash status item and is - * followed by a colon, the number of that status item is returned. Otherwise, - * this function returns \p -E_UNKNOWN_STAT_ITEM. + * \return Negative on errors, the number of bytes _not_ passed to \a + * item_handler. + * + * Status items are expected in the format used by parser-friendly output mode + * of the stat command of para_client/para_audioc. */ -int stat_line_valid(const char *line) +int for_each_stat_item(char *item_buf, size_t num_bytes, + int (*item_handler)(int, char *)) { - int i; - size_t line_len; - - if (!line || !*line) - return -E_UNKNOWN_STAT_ITEM; - line_len = strlen(line); - FOR_EACH_STATUS_ITEM(i) { - const char *s = status_item_list[i]; - size_t item_len = strlen(s); - - if (line_len > item_len && line[item_len] == ':' && - !strncmp(line, s, item_len)) - return i; + char *buf = item_buf; + int len = num_bytes; + + for (;;) { + int i, ret, item_len, item_num = 0; + if (len < MIN_STAT_ITEM_LEN) + break; + ret = read_size_header(buf); + if (ret < 0) + return ret; + item_len = ret; + if (item_len > len - 5) /* item not complete */ + break; + for (i = 0; i < 2; i++) { + unsigned char c = buf[5 + i]; + item_num <<= 4; + if (c >= '0' && c <= '9') { + item_num += c - '0'; + continue; + } + if (c >= 'a' && c <= 'f') { + item_num += c - 'a' + 10; + continue; + } + return -E_STAT_ITEM_PARSE; + } + if (buf[7] != ':' || buf[5 + item_len - 1] != '\n') + return -E_STAT_ITEM_PARSE; + buf[5 + item_len - 1] = '\0'; + if (item_num >= NUM_STAT_ITEMS) + PARA_WARNING_LOG("unknown status item %d: %s\n", + item_num, buf + 8); + else { + ret = item_handler(item_num, buf + 8); + if (ret < 0) + return ret; + } + buf += 5 + item_len; + len -= 5 + item_len; + assert(len >= 0 && buf <= item_buf + num_bytes); } - return -E_UNKNOWN_STAT_ITEM; + assert(len >= 0); + if (len && len != num_bytes) + memmove(item_buf, item_buf + num_bytes - len, len); + return len; } - diff --git a/string.c b/string.c index f51f3574..38c68ecb 100644 --- a/string.c +++ b/string.c @@ -200,15 +200,15 @@ __must_check __malloc char *para_dirname(const char *name) * ends with a slash. Otherwise, a pointer within \a name is returned. Caller * must not free the result. */ -__must_check const char *para_basename(const char *name) +__must_check char *para_basename(const char *name) { - const char *ret; + char *ret; if (!name || !*name) return NULL; ret = strrchr(name, '/'); if (!ret) - return name; + return (char *)name; ret++; return ret; } @@ -457,6 +457,40 @@ int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, private_data); } +#define hex(a) (hexchar[(a) & 15]) +static void write_size_header(char *buf, int n) +{ + static char hexchar[] = "0123456789abcdef"; + + buf[0] = hex(n >> 12); + buf[1] = hex(n >> 8); + buf[2] = hex(n >> 4); + buf[3] = hex(n); + buf[4] = ' '; +} + +int read_size_header(const char *buf) +{ + int i, len = 0; + + for (i = 0; i < 4; i++) { + unsigned char c = buf[i]; + len <<= 4; + if (c >= '0' && c <= '9') { + len += c - '0'; + continue; + } + if (c >= 'a' && c <= 'f') { + len += c - 'a' + 10; + continue; + } + return -E_SIZE_PREFIX; + } + if (buf[4] != ' ') + return -E_SIZE_PREFIX; + return len; +} + /** * Safely print into a buffer at a given offset. * @@ -472,7 +506,8 @@ int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, * private_data pointer of \a b are passed to the \a max_size_handler of \a b. * If this function succeeds, i.e. returns a non-negative value, the offset of * \a b is reset to zero and the given data is written to the beginning of the - * buffer. + * buffer. If \a max_size_handler() returns a negative value, this value is + * returned by \a para_printf(). * * Upon return, the offset of \a b is adjusted accordingly so that subsequent * calls to this function append data to what is already contained in the @@ -482,13 +517,15 @@ int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, * initial buffer is allocated. * * \return The number of bytes printed into the buffer (not including the - * terminating \p NULL byte). + * terminating \p NULL byte) on success, negative on errors. If there is no + * size-bound on \a b, i.e. if \p b->max_size is zero, this function never + * fails. * * \sa make_message(), vsnprintf(3). */ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) { - int ret; + int ret, sz_off = (b->flags & PBF_SIZE_PREFIX)? 5 : 0; if (!b->buf) { b->buf = para_malloc(128); @@ -499,13 +536,16 @@ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) char *p = b->buf + b->offset; size_t size = b->size - b->offset; va_list ap; - if (size) { + + if (size > sz_off) { va_start(ap, fmt); - ret = vsnprintf(p, size, fmt, ap); + ret = vsnprintf(p + sz_off, size - sz_off, fmt, ap); va_end(ap); - if (ret > -1 && ret < size) { /* success */ - b->offset += ret; - return ret; + if (ret > -1 && ret < size - sz_off) { /* success */ + b->offset += ret + sz_off; + if (sz_off) + write_size_header(p, ret); + return ret + sz_off; } } /* check if we may grow the buffer */ diff --git a/string.h b/string.h index 272f107d..38e5edc9 100644 --- a/string.h +++ b/string.h @@ -6,6 +6,12 @@ /** \file string.h exported sybmols from string.c */ +/** Flags that change how content is printed into the buffer. */ +enum para_buffer_flags { + /** Prefix each buffer with its length. */ + PBF_SIZE_PREFIX = 1, +}; + /** A string buffer used for para_printf(). */ struct para_buffer { /** The buffer. May be \p NULL. */ @@ -14,6 +20,8 @@ struct para_buffer { size_t size; /** The maximal size this buffer may grow. Zero means unlimited. */ size_t max_size; + /** \sa para_buffer_flags. */ + unsigned flags; /** The next para_printf() will write at this offset. */ size_t offset; /** @@ -26,6 +34,28 @@ struct para_buffer { void *private_data; }; +/** + * Write the contents of a status item to a para_buffer. + * + * \param b The para_buffer. + * \param n The number of the status item. + * \param f A format string. + * + * \return The return value of the underlying call to para_printf(). + */ +#define WRITE_STATUS_ITEM(b, n, f, ...) (\ +{ \ + int _ret; \ + if ((b)->flags & PBF_SIZE_PREFIX) { \ + _ret = para_printf((b), "%02x:" f, n, ## __VA_ARGS__); \ + } else { \ + _ret = para_printf((b), "%s: " f, status_item_list[(n)], \ + ## __VA_ARGS__); \ + } \ + _ret; \ +} \ +) + __must_check __malloc void *para_realloc(void *p, size_t size); __must_check __malloc void *para_malloc(size_t size); __must_check __malloc void *para_calloc(size_t size); @@ -33,7 +63,7 @@ __must_check __malloc char *para_strdup(const char *s); __must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...); __must_check __malloc char *para_strcat(char *a, const char *b); __must_check __malloc char *para_dirname(const char *name); -__must_check const char *para_basename(const char *name); +__must_check char *para_basename(const char *name); void chop(char *buf); __must_check __malloc char *para_tmpname(void); __must_check __malloc char *para_logname(void); @@ -50,3 +80,4 @@ int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, int para_atoi64(const char *str, int64_t *result); int para_atoi32(const char *str, int32_t *value); int get_loglevel_by_name(const char *txt); +int read_size_header(const char *buf); -- 2.39.2