From: Andre Noll Date: Thu, 10 Feb 2022 15:34:19 +0000 (+0100) Subject: Merge branch 'refs/heads/t/rm_v1_moods' X-Git-Tag: v0.7.0~8 X-Git-Url: http://git.tuebingen.mpg.de/?a=commitdiff_plain;h=88bf6848d1c58ad0e0d9b62d7da2a81cea5bf0ff;hp=184ea897a9b446611a9d02315160b6d77c0926fe;p=paraslash.git Merge branch 'refs/heads/t/rm_v1_moods' A single commit which removes a long obsolete feature. Cooking for almost a year. * refs/heads/t/rm_v1_moods: Remove support for version 1 moods. --- diff --git a/INSTALL b/INSTALL index 4a86e967..45676b7e 100644 --- a/INSTALL +++ b/INSTALL @@ -18,7 +18,7 @@ Installing paraslash from tarball Installing paraslash from git ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - autoconf && autoheader && make && sudo make install + autoconf && autoheader && ./configure && make && sudo make install Example for cross-compiling ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Makefile.in b/Makefile.in index d4a83a77..11fa2001 100644 --- a/Makefile.in +++ b/Makefile.in @@ -67,4 +67,6 @@ curses_ldflags := @curses_ldflags@ crypto_ldflags := @crypto_ldflags@ iconv_ldflags := @iconv_ldflags@ +ENABLE_UBSAN := @ENABLE_UBSAN@ + include Makefile.real diff --git a/Makefile.real b/Makefile.real index ddf85b58..b4af64e4 100644 --- a/Makefile.real +++ b/Makefile.real @@ -20,7 +20,7 @@ uname_s := $(shell uname -s 2>/dev/null || echo "UNKNOWN_OS") uname_rs := $(shell uname -rs) cc_version := $(shell $(CC) --version | head -n 1) GIT_VERSION := $(shell ./GIT-VERSION-GEN git-version.h) -COPYRIGHT_YEAR := 2020 +COPYRIGHT_YEAR := 2022 ifeq ("$(origin O)", "command line") build_dir := $(O) @@ -129,6 +129,11 @@ STRICT_CFLAGS += -Wno-sign-compare -Wno-unknown-pragmas STRICT_CFLAGS += -Wdeclaration-after-statement STRICT_CFLAGS += -Wformat -Wformat-security -Wmissing-format-attribute +ifeq ($(ENABLE_UBSAN), yes) + STRICT_CFLAGS += -fsanitize=undefined + LDFLAGS += -lubsan +endif + ifeq ($(uname_s),Linux) # these cause warnings on *BSD CPPFLAGS += -Wunused-macros diff --git a/NEWS.md b/NEWS.md index 3e3e7abf..a31aeaf1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,65 @@ NEWS ==== +--------------------------------------- +0.7.0 (to be announced) "seismic orbit" +--------------------------------------- + +- The new "duration" keyword of the mood grammar makes it possible to + impose a constraint on the duration of the admissible files. +- The long deprecated version 1 mood syntax is no longer supported. +- Paraslash writers handle early end-of-file more gracefully. +- The alsa writer no longer warns about spurious underruns. +- The score formula of the audio file selector has been reworked. +- Cleanups of the doubly linked lists code. +- New option for configure: --enable-ubsan to detect and report undefined + behaviour. +- The "tasks" server command has been removed. + +Downloads: +[tarball](./releases/paraslash-git.tar.xz) + +-------------------------------------- +0.6.4 (2021-11-04) "fuzzy calibration" +-------------------------------------- + +This point release contains a fair number of fixes but no new features. +This marks the end of the 0.6 development, although paraslash-0.6 will +still be supported for some time and subsequent maintenance releases +may follow. + +- The udp sender no longer crashes when empty chunks are encountered. +- Fix a double-free bug in the exit path of the server. +- The "jmp" command now errors out when given a negative percentage. +- A fix for a bug in para_afh which triggered on the attempt to modify + the tags of an invalid mp4 file. +- A memory leak in para_afh has been fixed. +- The udp sender no longer sends multiple EOF packets. +- Some gcc warnings have been silenced. +- Minor log level adjustments and documentation improvements. + +Downloads: +[tarball](./releases/paraslash-0.6.4.tar.xz), +[signature](./releases/paraslash-0.6.4.tar.xz.asc) + +--------------------------------------- +0.5.9 (2021-11-04) "reversed dimension" +--------------------------------------- + +This release contains a few important fixes which have accumulated in +the maint branch. The paraslash-0.5.x series has now reached its end +of life and will no longer be supported. All users should upgrade to +a more recent version at this point. + +- Fix an issue with the bash completion script. +- Initialize the random seed also when using libgrypt. +- Fix some compiler warnings in the resample filter +- Don't return spurious errors from the ff server command. + +Downloads: +[tarball](./releases/paraslash-0.5.9.tar.bz2), +[signature](./releases/paraslash-0.5.9.tar.bz2.asc) + ---------------------------------------- 0.6.3 (2021-02-18) "generalized activity" ----------------------------------------- diff --git a/aac_afh.c b/aac_afh.c index 7f2a22a2..2b3dd2cc 100644 --- a/aac_afh.c +++ b/aac_afh.c @@ -292,8 +292,7 @@ static int aac_afh_rewrite_tags(const char *map, size_t mapsize, mp4ff_tag_t *tag = metadata.tags + i; ret = -E_MP4FF_META_READ; - if (mp4ff_meta_get_by_index(mp4ff, i, - &tag->item, &tag->value) < 0) + if (!mp4ff_meta_get_by_index(mp4ff, i, &tag->item, &tag->value)) goto free_tags; PARA_INFO_LOG("found: %s: %s\n", tag->item, tag->value); if (!strcmp(tag->item, "artist")) @@ -318,7 +317,7 @@ static int aac_afh_rewrite_tags(const char *map, size_t mapsize, if (!found_comment) add_tag(&metadata, "comment", tags->comment); ret = -E_MP4FF_META_WRITE; - if (mp4ff_meta_update(&cb, &metadata) < 0) + if (!mp4ff_meta_update(&cb, &metadata)) goto free_tags; ret = 1; free_tags: diff --git a/afh.c b/afh.c index 567b560a..c896a7d1 100644 --- a/afh.c +++ b/afh.c @@ -258,5 +258,6 @@ out: PARA_ERROR_LOG("%s\n", errctx); if (ret < 0) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + lls_free_parse_result(lpr, CMD_PTR); return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/afh.h b/afh.h index 881db3c2..b3295f6e 100644 --- a/afh.h +++ b/afh.h @@ -58,8 +58,6 @@ struct afh_info { /** Data about the current audio file, passed from afs to server. */ struct audio_file_data { - /** The open file descriptor to the current audio file. */ - int fd; /** Vss needs this for streaming. */ struct afh_info afhi; /** diff --git a/afs.c b/afs.c index c4de2e8f..3d86d192 100644 --- a/afs.c +++ b/afs.c @@ -418,13 +418,13 @@ static int pass_afd(int fd, char *buf, size_t size) */ static int open_next_audio_file(void) { - struct audio_file_data afd; - int ret, shmid; + int ret, shmid, fd; char buf[8]; - ret = open_and_update_audio_file(&afd); + ret = open_and_update_audio_file(&fd); if (ret < 0) { - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + if (ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); goto no_admissible_files; } shmid = ret; @@ -434,8 +434,8 @@ static int open_next_audio_file(void) } *(uint32_t *)buf = NEXT_AUDIO_FILE; *(uint32_t *)(buf + 4) = (uint32_t)shmid; - ret = pass_afd(afd.fd, buf, 8); - close(afd.fd); + ret = pass_afd(fd, buf, 8); + close(fd); if (ret >= 0) return ret; destroy: @@ -981,7 +981,7 @@ __noreturn void afs_init(int socket_fd) int i, ret; register_signal_task(&s); - INIT_LIST_HEAD(&afs_client_list); + init_list_head(&afs_client_list); for (i = 0; i < NUM_AFS_TABLES; i++) afs_tables[i].init(&afs_tables[i]); ret = open_afs_tables(); diff --git a/afs.h b/afs.h index cfa9cc6d..b1606493 100644 --- a/afs.h +++ b/afs.h @@ -255,7 +255,7 @@ int attribute_check_callback(struct afs_callback_arg *aca); void aft_init(struct afs_table *t); int aft_get_row_of_path(const char *path, struct osl_row **row); int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb); -int open_and_update_audio_file(struct audio_file_data *afd); +int open_and_update_audio_file(int *fd); int load_afd(int shmid, struct audio_file_data *afd); int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi); int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi); diff --git a/aft.c b/aft.c index eb955e01..c8c98e7a 100644 --- a/aft.c +++ b/aft.c @@ -721,7 +721,8 @@ __a_const static short unsigned get_duration_width(int seconds) return width + 6; } -static void get_duration_buf(int seconds, char *buf, struct ls_options *opts) +static void get_duration_buf(int seconds, char *buf, size_t bufsize, + struct ls_options *opts) { unsigned hours = seconds / 3600, mins = (seconds % 3600) / 60; short unsigned max_width; @@ -729,10 +730,12 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts) if (!hours) { /* m:ss or mm:ss */ max_width = opts->mode == LS_MODE_LONG? opts->widths.duration_width : 4; + assert(max_width < bufsize - 1); sprintf(buf, "%*u:%02d", max_width - 3, mins, seconds % 60); } else { /* more than one hour => h:mm:ss, hh:mm:ss, hhh:mm:ss, ... */ max_width = opts->mode == LS_MODE_LONG? opts->widths.duration_width : 7; + assert(max_width < bufsize - 1); sprintf(buf, "%*u:%02u:%02d", max_width - 6, hours, mins, seconds % 60); } @@ -856,7 +859,8 @@ 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); + get_duration_buf(afhi->seconds_total, duration_buf, + sizeof(duration_buf), opts); if (opts->mode == LS_MODE_LONG) { struct ls_widths *w = &opts->widths; if (lls_opt_given(r_a)) @@ -1020,7 +1024,14 @@ out: /** * Open the audio file with highest score and set up an afd structure. * - * \param afd Result pointer. + * This determines and opens the next audio file, verifies that it did not + * change by comparing the recomputed the hash value of the file contents + * against the value stored in the audio file table. If all goes well, it + * creates a shared memory area containing the serialized version of the afd + * structure, including the chunk table, if any. The caller can then send the + * ID of this area and the open fd to the server process. + * + * \param fd Result pointer for the file descriptor of the audio file. * * On success, the numplayed field of the audio file selector info is increased * and the lastplayed time is set to the current time. Finally, the score of @@ -1028,7 +1039,7 @@ out: * * \return Positive shmid on success, negative on errors. */ -int open_and_update_audio_file(struct audio_file_data *afd) +int open_and_update_audio_file(int *fd) { unsigned char file_hash[HASH_SIZE]; struct osl_object afsi_obj; @@ -1038,6 +1049,7 @@ int open_and_update_audio_file(struct audio_file_data *afd) struct osl_object map, chunk_table_obj; struct ls_data *d = &status_item_ls_data; unsigned char *tmp_hash; + struct audio_file_data afd; again: ret = score_get_best(¤t_aft_row, &d->score); if (ret < 0) @@ -1070,11 +1082,11 @@ again: ret = load_afsi(&d->afsi, &afsi_obj); if (ret < 0) return ret; - ret = get_afhi_of_row(current_aft_row, &afd->afhi); + ret = get_afhi_of_row(current_aft_row, &afd.afhi); if (ret < 0) return ret; - d->afhi = afd->afhi; - d->afhi.chunk_table = afd->afhi.chunk_table = NULL; + d->afhi = afd.afhi; + d->afhi.chunk_table = afd.afhi.chunk_table = NULL; ret = osl(osl_open_disk_object(audio_file_table, current_aft_row, AFTCOL_CHUNKS, &chunk_table_obj)); if (ret < 0) { @@ -1086,7 +1098,7 @@ again: } else { PARA_INFO_LOG("chunk table: %zu bytes\n", chunk_table_obj.size); } - ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd); + ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, fd); if (ret < 0) goto out; hash_function(map.data, map.size, file_hash); @@ -1101,8 +1113,8 @@ again: new_afsi.last_played = time(NULL); save_afsi(&new_afsi, &afsi_obj); /* in-place update */ - afd->audio_format_id = d->afsi.audio_format_id; - load_chunk_table(&afd->afhi, &chunk_table_obj); + afd.audio_format_id = d->afsi.audio_format_id; + load_chunk_table(&afd.afhi, &chunk_table_obj); aced.aft_row = current_aft_row; aced.old_afsi = &d->afsi; /* @@ -1112,9 +1124,9 @@ again: ret = afs_event(AFSI_CHANGE, NULL, &aced); if (ret < 0) goto out; - ret = save_afd(afd); + ret = save_afd(&afd); out: - free(afd->afhi.chunk_table); + free(afd.afhi.chunk_table); if (chunk_table_obj.data) osl_close_disk_object(&chunk_table_obj); if (ret < 0) { diff --git a/alsa_write.c b/alsa_write.c index bc06fc31..bbbf8b65 100644 --- a/alsa_write.c +++ b/alsa_write.c @@ -240,6 +240,8 @@ static void alsa_close(struct writer_node *wn) if (!pad) return; + if (!pad->handle) + goto free_pad; /* * It's OK to have a blocking operation here because we already made * sure that the PCM output buffer is (nearly) empty. @@ -248,6 +250,7 @@ static void alsa_close(struct writer_node *wn) snd_pcm_drain(pad->handle); snd_pcm_close(pad->handle); snd_config_update_free_global(); +free_pad: free(pad); } @@ -294,21 +297,23 @@ again: if (bytes == 0) /* no data available */ return 0; pad = wn->private_data = para_calloc(sizeof(*pad)); - get_btr_sample_rate(btrn, &val); + ret = get_btr_sample_rate(btrn, &val); + if (ret < 0) + goto err; pad->sample_rate = val; - get_btr_channels(btrn, &val); + ret = get_btr_channels(btrn, &val); + if (ret < 0) + goto err; pad->channels = val; - get_btr_sample_format(btrn, &val); + ret = get_btr_sample_format(btrn, &val); + if (ret < 0) + goto err; pad->sample_format = get_alsa_pcm_format(val); - PARA_INFO_LOG("%u channel(s), %uHz\n", pad->channels, pad->sample_rate); ret = alsa_init(wn); - if (ret < 0) { - free(wn->private_data); - wn->private_data = NULL; + if (ret < 0) goto err; - } wn->min_iqs = pad->bytes_per_frame; goto again; } @@ -326,7 +331,11 @@ again: goto again; } if (frames == -EPIPE) { - PARA_WARNING_LOG("underrun (tried to write %zu bytes)\n", bytes); + snd_pcm_status_t *status; + snd_pcm_status_malloc(&status); + if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) + PARA_WARNING_LOG("underrun\n"); + snd_pcm_status_free(status); snd_pcm_prepare(pad->handle); return 0; } diff --git a/ao_write.c b/ao_write.c index 447dea84..037b9299 100644 --- a/ao_write.c +++ b/ao_write.c @@ -357,9 +357,15 @@ static int aow_post_select(__a_unused struct sched *s, void *context) goto remove_btrn; if (ret == 0) return 0; - get_btr_sample_rate(wn->btrn, &rate); - get_btr_channels(wn->btrn, &ch); - get_btr_sample_format(wn->btrn, &format); + ret = get_btr_sample_rate(wn->btrn, &rate); + if (ret < 0) + goto remove_btrn; + ret = get_btr_channels(wn->btrn, &ch); + if (ret < 0) + goto remove_btrn; + ret = get_btr_sample_format(wn->btrn, &format); + if (ret < 0) + goto remove_btrn; ret = aow_init(wn, rate, ch, format); if (ret < 0) goto remove_btrn; diff --git a/audiod.c b/audiod.c index a5a77437..88599c3f 100644 --- a/audiod.c +++ b/audiod.c @@ -801,7 +801,7 @@ static int parse_stream_command(const char *txt, const char **cmd) return -E_MISSING_COLON; *cmd = p + 1; len = p - txt; - re = malloc(len + 1); + re = para_malloc(len + 1); strncpy(re, txt, len); re[len] = '\0'; ret = get_matching_audio_format_nums(re); diff --git a/buffer_tree.c b/buffer_tree.c index 8a317513..f0d2002d 100644 --- a/buffer_tree.c +++ b/buffer_tree.c @@ -270,8 +270,8 @@ struct btr_node *btr_new_node(struct btr_node_description *bnd) btrn->context = bnd->context; btrn->start.tv_sec = 0; btrn->start.tv_usec = 0; - INIT_LIST_HEAD(&btrn->children); - INIT_LIST_HEAD(&btrn->input_queue); + init_list_head(&btrn->children); + init_list_head(&btrn->input_queue); if (!bnd->child) { if (bnd->parent) { list_add_tail(&btrn->node, &bnd->parent->children); diff --git a/chunk_queue.c b/chunk_queue.c index 08f57e9d..cf74cc33 100644 --- a/chunk_queue.c +++ b/chunk_queue.c @@ -131,7 +131,7 @@ int cq_get(struct queued_chunk *qc, const char **buf, size_t *num_bytes) struct chunk_queue *cq_new(size_t max_pending) { struct chunk_queue *cq = para_malloc(sizeof(*cq)); - INIT_LIST_HEAD(&cq->q); + init_list_head(&cq->q); cq->max_pending = max_pending; cq->num_pending = 0; return cq; diff --git a/client.c b/client.c index f72719f2..8caf4483 100644 --- a/client.c +++ b/client.c @@ -100,7 +100,7 @@ static int create_merged_lpr(const char *line) /* * The original lpr for the interactive session has no non-option * arguments. We create a fresh lpr from the words in "line" and merge - * it with the orignal lpr. + * it with the original lpr. */ ret = lls(lls_parse(argc, argv, cmd, &argv_lpr, &errctx)); free_argv(argv); @@ -243,7 +243,6 @@ I9E_DUMMY_COMPLETER(term); I9E_DUMMY_COMPLETER(stop); I9E_DUMMY_COMPLETER(addatt); I9E_DUMMY_COMPLETER(init); -I9E_DUMMY_COMPLETER(tasks); static struct i9e_completer completers[]; diff --git a/client_common.c b/client_common.c index dc24a628..c25da96b 100644 --- a/client_common.c +++ b/client_common.c @@ -50,13 +50,12 @@ void client_close(struct client_task *ct) } /* - * The preselect hook for server commands. + * This function asks the scheduler to monitor a file descriptor which + * corresponds to an active connection. The descriptor is monitored for either + * reading or writing, depending on the state of the connection. * - * The task pointer must contain a pointer to the initialized client data - * structure as it is returned by client_open(). - * - * This function checks the state of the connection and adds the file descriptor - * of the connection to the read or write fd set of s accordingly. + * The context pointer is assumed to refer to a client task structure that was + * initialized earlier by client_open(). */ static void client_pre_select(struct sched *s, void *context) { @@ -260,12 +259,15 @@ static int send_sb_command(struct client_task *ct) } /* - * The post select hook for client commands. + * This function reads or writes to the socket file descriptor which + * corresponds to an established connection between the client and the server. + * It depends on the current state of the connection and on the readiness of + * the socket file descriptor which type of I/O is going to be performed. + * Besides the initial handshake and authentication, the function sends the + * server command and receives the output from the server, if any. * - * Depending on the current state of the connection and the status of the read - * and write fd sets of s, this function performs the necessary steps to - * authenticate the connection, to send the command given by t->private_data - * and to receive para_server's output, if any. + * The context pointer refers to a client task structure that was initialized + * earlier by client_open(). */ static int client_post_select(struct sched *s, void *context) { diff --git a/close_on_fork.c b/close_on_fork.c index 28c5eabb..7b464d09 100644 --- a/close_on_fork.c +++ b/close_on_fork.c @@ -34,7 +34,7 @@ void add_close_on_fork_list(int fd) struct close_on_fork *cof = para_malloc(sizeof(struct close_on_fork)); if (!initialized) { - INIT_LIST_HEAD(&close_on_fork_list); + init_list_head(&close_on_fork_list); initialized = 1; } cof->fd = fd; diff --git a/command.c b/command.c index 8ea725de..7b2660e3 100644 --- a/command.c +++ b/command.c @@ -6,10 +6,7 @@ #include #include #include -#include -#include #include -#include #include #include @@ -22,7 +19,6 @@ #include "command.h" #include "string.h" #include "afh.h" -#include "afs.h" #include "net.h" #include "server.h" #include "list.h" @@ -554,7 +550,7 @@ out: } EXPORT_SERVER_CMD_HANDLER(stat); -const char *aux_info_cb(unsigned cmd_num, bool verbose) +static const char *aux_info_cb(unsigned cmd_num, bool verbose) { static char result[80]; unsigned perms = server_command_perms[cmd_num]; @@ -717,8 +713,7 @@ EXPORT_SERVER_CMD_HANDLER(ff); static int com_jmp(struct command_context *cc, struct lls_parse_result *lpr) { - long unsigned int i; - int ret; + int i, ret; char *errctx; ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); @@ -726,18 +721,16 @@ static int com_jmp(struct command_context *cc, struct lls_parse_result *lpr) send_errctx(cc, errctx); return ret; } - if (sscanf(lls_input(0, lpr), "%lu", &i) <= 0) + if (sscanf(lls_input(0, lpr), "%d", &i) <= 0) + return -ERRNO_TO_PARA_ERROR(EINVAL); + if (i < 0 || i > 100) return -ERRNO_TO_PARA_ERROR(EINVAL); mutex_lock(mmd_mutex); ret = -E_NO_AUDIO_FILE; if (!mmd->afd.afhi.chunks_total) goto out; - if (i > 100) - i = 100; - PARA_INFO_LOG("jumping to %lu%%\n", i); + PARA_INFO_LOG("jumping to %d%%\n", i); mmd->repos_request = (mmd->afd.afhi.chunks_total * i + 50) / 100; - PARA_INFO_LOG("sent: %lu, offset before jmp: %li\n", - mmd->chunks_sent, mmd->offset); mmd->new_vss_status_flags |= VSS_REPOS; mmd->new_vss_status_flags &= ~VSS_NEXT; ret = 1; @@ -748,14 +741,6 @@ out: } EXPORT_SERVER_CMD_HANDLER(jmp); -/* deprecated, does nothing */ -static int com_tasks(__a_unused struct command_context *cc, - __a_unused struct lls_parse_result *lpr) -{ - return 1; -} -EXPORT_SERVER_CMD_HANDLER(tasks); - static void reset_signals(void) { para_sigaction(SIGCHLD, SIG_IGN); diff --git a/compress_filter.c b/compress_filter.c index 15bed6df..ff4ce6fb 100644 --- a/compress_filter.c +++ b/compress_filter.c @@ -79,7 +79,7 @@ next_buffer: sample *= pcd->current_gain; sample >>= inertia + 1; if (sample > 32767) { /* clip */ - PARA_WARNING_LOG("clip: %d\n", sample); + PARA_NOTICE_LOG("clip: %d\n", sample); sample = 32767; pcd->current_gain = (3 * pcd->current_gain + (1 << inertia)) / 4; diff --git a/configure.ac b/configure.ac index 24009858..99e82b28 100644 --- a/configure.ac +++ b/configure.ac @@ -373,6 +373,11 @@ AC_CHECK_HEADER(samplerate.h, [], HAVE_SAMPLERATE=no) AC_CHECK_LIB([samplerate], [src_process], [], HAVE_SAMPLERATE=no) LIB_SUBST_FLAGS(samplerate) UNSTASH_FLAGS +######################################################################### ubsan +AC_ARG_ENABLE([ubsan], [AS_HELP_STRING(--enable-ubsan, + [Detect and report undefined behaviour.])], + [ENABLE_UBSAN=yes], [ENABLE_UBSAN=no]) +AC_SUBST(ENABLE_UBSAN) ######################################################################### server if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \ test -n "$FLEX"; then diff --git a/daemon.c b/daemon.c index a4e2f319..dd5420a6 100644 --- a/daemon.c +++ b/daemon.c @@ -58,9 +58,9 @@ static void daemon_set_default_log_colors(void) } /** - * Set the color for one loglevel. + * Set the color for log messages of the given severity level. * - * \param arg Must be of the form "ll:[fg [bg]] [attr]". + * \param arg Must be of the form "severity:[fg [bg]] [attr]". */ void daemon_set_log_color_or_die(const char *arg) { diff --git a/error.h b/error.h index fe44ff5c..e9482121 100644 --- a/error.h +++ b/error.h @@ -166,7 +166,6 @@ PARA_ERROR(OGGDEC_VERSION, "vorbis version mismatch"), \ PARA_ERROR(OGG_EMPTY, "no ogg pages found"), \ PARA_ERROR(OGG_PACKET_IN, "ogg_stream_packetin() failed"), \ - PARA_ERROR(OGG_STREAM_FLUSH, "ogg_stream_flush() failed"), \ PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \ PARA_ERROR(OPENSSH_PARSE, "could not parse openssh private key"), \ PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \ @@ -253,6 +252,14 @@ * 'E_') and gets later redefined to expand to the error text only */ #define PARA_ERROR(err, msg) E_ ## err +/** + * Numeric error codes. + * + * Public functions which can fail return the negated value of one of the + * constants defined here to indicate the cause of the error. + * + * \sa \ref para_strerror(). + */ enum para_error_codes {PARA_ERRORS}; #undef PARA_ERROR #define PARA_ERROR(err, msg) msg diff --git a/interactive.c b/interactive.c index a8197308..8c4545b4 100644 --- a/interactive.c +++ b/interactive.c @@ -45,7 +45,7 @@ static struct i9e_private i9e_private, *i9ep = &i9e_private; * running. * * \return A negative return value of zero means the i9e task terminated. Only - * in this case it is safe to call ie9_close(). + * in this case it is safe to call i9e_close(). */ int i9e_get_error(void) { @@ -556,7 +556,7 @@ __printf_2_3 void i9e_log(int ll, const char* fmt,...) * the given text. If the length of this text exceeds the width of the * terminal, the text is shortened by leaving out a part in the middle. */ -void ie9_print_status_bar(char *buf, unsigned len) +void i9e_print_status_bar(char *buf, unsigned len) { size_t x = i9ep->num_columns, y = (x - 4) / 2; diff --git a/interactive.h b/interactive.h index 40ff2940..ddf02d76 100644 --- a/interactive.h +++ b/interactive.h @@ -80,7 +80,7 @@ struct i9e_client_info { int i9e_open(struct i9e_client_info *ici, struct sched *s); void i9e_attach_to_stdout(struct btr_node *producer); -void ie9_print_status_bar(char *buf, unsigned len); +void i9e_print_status_bar(char *buf, unsigned len); void i9e_close(void); void i9e_signal_dispatch(int sig_num); __printf_2_3 void i9e_log(int ll, const char* fmt,...); diff --git a/list.h b/list.h index 66c6d915..78c302fa 100644 --- a/list.h +++ b/list.h @@ -2,166 +2,134 @@ * Copied from the Linux kernel source tree, version 2.6.13. * * Licensed under the GPL v2 as per the whole kernel source tree. - * */ -/** \file list.h doubly linked list implementation */ +/** \file list.h Doubly linked list implementation. */ #include /* offsetof */ -/** get the struct this entry is embedded in */ +/** Get the struct this entry is embedded in. */ #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) -/** - * Non-NULL pointers that will result in page faults under normal - * circumstances, used to verify that nobody uses non-initialized list entries. - * Used for poisoning the \a next pointer of struct list_head. - */ -#define LIST_POISON1 ((void *) 0x00100100) -/** Non-null pointer, used for poisoning the \a prev pointer of struct - * list_head - */ -#define LIST_POISON2 ((void *) 0x00200200) - -/** Simple doubly linked list implementation. */ +/** A list head is just a pair of pointers. */ struct list_head { - /** pointer to the next list entry */ + /** Pointer to the next list entry. */ struct list_head *next; - /** pointer to the previous list entry */ + /** Pointer to the previous list entry. */ struct list_head *prev; }; /** Define an initialized list head. */ -#define INITIALIZED_LIST_HEAD(name) struct list_head name = { &(name), &(name) } - - -/** must be called before using any other list functions */ -#define INIT_LIST_HEAD(ptr) do { \ - (ptr)->next = (ptr); (ptr)->prev = (ptr); \ -} while (0) +#define INITIALIZED_LIST_HEAD(name) struct list_head name = {&(name), &(name)} - -/* - * Some of the internal functions ("__xxx") are useful when - * manipulating whole lists rather than single entries, as - * sometimes we already know the next/prev entries and we can - * generate better code by using them directly rather than - * using the generic single-entry routines. - */ - - -/* - * Insert a new entry between two known consecutive entries. - * - * This is only for internal list manipulation where we know - * the prev/next entries already! - */ -static inline void __list_add(struct list_head *new, - struct list_head *prev, - struct list_head *next) +/** This must be called before using any other list functions. */ +static inline void init_list_head(struct list_head *head) { - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; + head->next = head; + head->prev = head; } /** - * add a new entry + * Insert a new entry after the specified head. * - * \param new new entry to be added - * \param head list head to add it after + * \param entry The new entry to add. + * \param head The list head to add it after. * - * Insert a new entry after the specified head. * This is good for implementing stacks. */ -static inline void para_list_add(struct list_head *new, struct list_head *head) +static inline void para_list_add(struct list_head *entry, struct list_head *head) { - __list_add(new, head, head->next); + entry->prev = head; + entry->next = head->next; + head->next->prev = entry; + head->next = entry; } /** - * add a new entry + * Insert a new entry before the specified head. * - * \param new new entry to be added - * \param head list head to add it before + * \param entry The new entry to add. + * \param head list head to add it before. * - * Insert a new entry before the specified head. * This is useful for implementing queues. */ -static inline void list_add_tail(struct list_head *new, struct list_head *head) +static inline void list_add_tail(struct list_head *entry, struct list_head *head) { - __list_add(new, head->prev, head); + entry->prev = head->prev; + entry->next = head; + head->prev->next = entry; + head->prev = entry; } -/* - * Delete a list entry by making the prev/next entries - * point to each other. +/** + * Delete an entry from a list. + * + * \param entry The element to delete. * - * This is only for internal list manipulation where we know - * the prev/next entries already! + * The list entry is in an undefined state after this and \ref list_empty() + * does not return true. */ -static inline void __list_del(struct list_head * prev, struct list_head * next) +static inline void list_del(struct list_head *entry) { - next->prev = prev; - prev->next = next; + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + /* + * These non-NULL pointers result in page faults when dereferenced. + * This helps to catch bugs resulting from using deleted list heads. + */ + entry->next = (void *)0x00100100; + entry->prev = (void *)0x00200200; } /** - * Delete entry from list. - * - * \param entry the element to delete from the list. + * Delete an entry from one list and add it as another list's head. * - * Note: list_empty on entry does not return true after this, the entry is - * in an undefined state. + * \param entry The entry to move. + * \param head The head that will precede our entry. */ -static inline void list_del(struct list_head *entry) +static inline void list_move(struct list_head *entry, struct list_head *head) { - __list_del(entry->prev, entry->next); - entry->next = LIST_POISON1; - entry->prev = LIST_POISON2; + list_del(entry); + para_list_add(entry, head); } /** - * delete from one list and add as another's head + * Test whether a list contains no entries. * - * \param list: the entry to move - * \param head: the head that will precede our entry + * \param head The list to test. */ -static inline void list_move(struct list_head *list, struct list_head *head) +static inline int list_empty(const struct list_head *head) { - __list_del(list->prev, list->next); - para_list_add(list, head); + return head->next == head; } /** - * test whether a list is empty + * Test whether a list has just one entry. * - * \param head the list to test. + * \param head The list to test. */ -static inline int list_empty(const struct list_head *head) +static inline int list_is_singular(const struct list_head *head) { - return head->next == head; + return !list_empty(head) && (head->next == head->prev); } /** - * get the struct for this entry + * Get the struct in which this entry is embedded in. * - * \param ptr the &struct list_head pointer. - * \param type the type of the struct this is embedded in. - * \param member the name of the list_struct within the struct. + * \param ptr The list head pointer. + * \param type The type of containing structure. + * \param member The name of the list head member within the structure. */ -#define list_entry(ptr, type, member) \ - container_of(ptr, type, member) +#define list_entry(ptr, type, member) container_of(ptr, type, member) /** - * iterate over list of given type + * Iterate over a list. * - * \param pos the type * to use as a loop counter. - * \param head the head for your list. - * \param member the name of the list_struct within the struct. + * \param pos A struct pointer which serves as the iterator. + * \param head The head of the list. + * \param member The name of the list head member within the structure. */ #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member); \ @@ -169,49 +137,27 @@ static inline int list_empty(const struct list_head *head) pos = list_entry(pos->member.next, typeof(*pos), member)) /** - * iterate over list of given type safe against removal of list entry + * Iterate over list, safe against removal of list entry. * - * \param pos the type * to use as a loop counter. - * \param n another type * to use as temporary storage - * \param head the head for your list. - * \param member the name of the list_struct within the struct. + * \param pos The iterator struct pointer. + * \param n A second struct pointer which is used as temporary storage. + * \param head The head of the list. + * \param member The name of the list head member within the structure. */ #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) -/** - * iterate backwards over list of given type safe against removal of list entry - * \param pos the type * to use as a loop counter. - * \param n another type * to use as temporary storage - * \param head the head for your list. - * \param member the name of the list_struct within the struct. - */ -#define list_for_each_entry_safe_reverse(pos, n, head, member) \ - for (pos = list_entry((head)->prev, typeof(*pos), member), \ - n = list_entry(pos->member.prev, typeof(*pos), member); \ - &pos->member != (head); \ - pos = n, n = list_entry(n->member.prev, typeof(*n), member)) /** - * Get the first element from a list - * \param ptr the list head to take the element from. + * Get the first element of a list. + * + * \param ptr The list head to take the element from. * \param type The type of the struct this is embedded in. * \param member The name of the list_struct within the struct. * - * Note that list is expected to be not empty. + * Note that the list is expected to be non-empty. */ #define list_first_entry(ptr, type, member) \ - list_entry((ptr)->next, type, member) - -/** - * Test whether a list has just one entry. - * - * \param head The list to test. - */ -static inline int list_is_singular(const struct list_head *head) -{ - return !list_empty(head) && (head->next == head->prev); -} - + list_entry((ptr)->next, type, member) diff --git a/m4/lls/server_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4 index 2a39907c..f73d66b2 100644 --- a/m4/lls/server_cmd.suite.m4 +++ b/m4/lls/server_cmd.suite.m4 @@ -500,15 +500,6 @@ aux_info_prefix = Permissions: the vss status flags, effectively stopping playback. [/description] -[subcommand tasks] - purpose = list active server tasks (deprecated) - aux_info = NO_PERMISSION_REQUIRED - [description] - This used to print the ID, the status and the name of each task, - mainly for debugging purposes. As of version 0.6.2, the subcommand - prints nothing. It will be removed in 0.7.0. Don't use. - [/description] - [subcommand term] purpose = ask the server to terminate aux_info = VSS_READ | VSS_WRITE diff --git a/mood.c b/mood.c index ccc57a03..bbe84734 100644 --- a/mood.c +++ b/mood.c @@ -38,10 +38,16 @@ struct afs_statistics { int64_t num_played_qd; /** Quadratic deviation of last played time. */ int64_t last_played_qd; + /** Correction factor for the num played score. */ + int64_t num_played_correction; + /** Correction factor for the last played score. */ + int64_t last_played_correction; + /** Common divisor of the correction factors. */ + int64_t normalization_divisor; /** Number of admissible files */ unsigned num; }; -static struct afs_statistics statistics; +static struct afs_statistics statistics = {.normalization_divisor = 1}; struct mood { /** The name of this mood. */ @@ -204,6 +210,43 @@ int mood_check_callback(struct afs_callback_arg *aca) check_mood)); } +/* + * The normalized num_played and last_played values are defined as + * + * nn := -(np - mean_n) / sigma_n and nl := -(lp - mean_l) / sigma_l + * + * For a (hypothetical) file with np = 0 and lp = now we thus have + * + * nn = mean_n / sigma_n =: hn > 0 + * nl = -(now - mean_l) / sigma_l =: hl < 0 + * + * We design the score function so that both contributions get the same + * weight. Define the np and lp score of an arbitrary file as + * + * sn := nn * -hl and sl := nl * hn + * + * Example: + * num_played mean/sigma: 87/14 + * last_played mean/sigma: 45/32 days + * + * We have hn = 87 / 14 = 6.21 and hl = -45 / 32 = -1.41. Multiplying + * nn of every file with the correction factor 1.41 and nl with + * 6.21 makes the weight of the two contributions equal. + * + * The total score s := sn + sl has the representation + * + * s = -cn * (np - mean_n) - cl * (lp - mean_l) + * + * with positive correction factors + * + * cn = (now - mean_l) / (sqrt(ql) * sqrt(qn) / n) + * cl = mean_n / (sqrt(ql) * sqrt(qn) / n) + * + * where ql and qn are the quadratic deviations stored in the statistics + * structure and n is the number of admissible files. To avoid integer + * overflows and rounding errors we store the common divisor of the + * correction factors separately. + */ static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd) { if (!n || !qd) @@ -261,6 +304,7 @@ static int del_afs_statistics(const struct osl_row *row) assert(n); if (n == 1) { memset(&statistics, 0, sizeof(statistics)); + statistics.normalization_divisor = 1; return 1; } @@ -488,15 +532,11 @@ static int mood_update_audio_file(const struct osl_row *aft_row, return score_update(aft_row, percent); } -static void log_statistics(void) +/* sse: seconds since epoch. */ +static void log_statistics(int64_t sse) { unsigned n = statistics.num; int mean_days, sigma_days; - /* - * We can not use the "now" pointer from sched.c here because we are - * called before schedule(), which initializes "now". - */ - struct timeval rnow; assert(current_mood); PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name? @@ -506,13 +546,18 @@ static void log_statistics(void) return; } PARA_NOTICE_LOG("%u admissible files\n", statistics.num); - clock_get_realtime(&rnow); - mean_days = (rnow.tv_sec - statistics.last_played_sum / n) / 3600 / 24; + mean_days = (sse - statistics.last_played_sum / n) / 3600 / 24; sigma_days = int_sqrt(statistics.last_played_qd / n) / 3600 / 24; PARA_NOTICE_LOG("last_played mean/sigma: %d/%d days\n", mean_days, sigma_days); - PARA_NOTICE_LOG("num_played mean/sigma: %llu/%llu\n", - (long long unsigned)statistics.num_played_sum / n, - (long long unsigned)int_sqrt(statistics.num_played_qd / n)); + PARA_NOTICE_LOG("num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n", + statistics.num_played_sum / n, + int_sqrt(statistics.num_played_qd / n)); + PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n", + statistics.num_played_correction); + PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n", + statistics.last_played_correction); + PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n", + statistics.normalization_divisor); } /** @@ -525,6 +570,25 @@ void close_current_mood(void) destroy_mood(current_mood); current_mood = NULL; memset(&statistics, 0, sizeof(statistics)); + statistics.normalization_divisor = 1; +} + +static void compute_correction_factors(int64_t sse) +{ + struct afs_statistics *s = &statistics; + + if (s->num > 0) { + s->normalization_divisor = int_sqrt(s->last_played_qd) + * int_sqrt(s->num_played_qd) / s->num / 100; + s->num_played_correction = sse - s->last_played_sum / s->num; + s->last_played_correction = s->num_played_sum / s->num; + } + if (s->num_played_correction == 0) + s->num_played_correction = 1; + if (s->normalization_divisor == 0) + s->normalization_divisor = 1; + if (s->last_played_correction == 0) + s->last_played_correction = 1; } /** @@ -553,6 +617,11 @@ int change_current_mood(const char *mood_name, char **errmsg) .size = 0, .array = NULL }; + /* + * We can not use the "now" pointer from sched.c here because we are + * called before schedule(), which initializes "now". + */ + struct timeval rnow; if (mood_name) { struct mood *m; @@ -585,6 +654,9 @@ int change_current_mood(const char *mood_name, char **errmsg) *errmsg = make_message("audio file loop failed"); return ret; } + clock_get_realtime(&rnow); + compute_correction_factors(rnow.tv_sec); + log_statistics(rnow.tv_sec); for (i = 0; i < statistics.num; i++) { ret = add_to_score_table(aa.array[i]); if (ret < 0) { @@ -594,7 +666,6 @@ int change_current_mood(const char *mood_name, char **errmsg) goto out; } } - log_statistics(); ret = statistics.num; out: free(aa.array); diff --git a/mp.c b/mp.c index 56c16e31..b5fa9cac 100644 --- a/mp.c +++ b/mp.c @@ -389,6 +389,27 @@ MP_AFHI(frequency) MP_AFHI(channels) /** \endcond */ +/** + * Return the duration of the audio file from the afh info structure. + * + * \param ctx See \ref mp_path(). + * + * The duration is computed by multiplying the number of chunks and the + * duration of one chunk. + * + * \return The approximate number of milliseconds. + */ +int64_t mp_duration(struct mp_context *ctx) +{ + struct timeval tmp; + int ret = get_afhi(ctx); + + if (ret < 0) + return 0; + tv_scale(ctx->afhi.chunks_total, &ctx->afhi.chunk_tv, &tmp); + return tv2ms(&tmp); +} + /** * Define a function which extracts and returns the value of a meta tag. * @@ -541,6 +562,8 @@ bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx) { if (!ctx) /* dummy mood */ return true; + if (!ctx->ast) /* empty mood */ + return true; assert(aft_row); ctx->aft_row = aft_row; ctx->have_afsi = false; diff --git a/mp.h b/mp.h index febbe324..891bfb05 100644 --- a/mp.h +++ b/mp.h @@ -139,6 +139,7 @@ bool mp_is_set(const char *attr, struct mp_context *ctx); char *mp_path(struct mp_context *ctx); int64_t mp_year(struct mp_context *ctx); int64_t mp_num_attributes_set(struct mp_context *ctx); +int64_t mp_duration(struct mp_context *ctx); /* Generated with MP_AFSI() */ /** \cond MP_AFSI */ diff --git a/net.c b/net.c index 91200fc0..e1951e5e 100644 --- a/net.c +++ b/net.c @@ -288,7 +288,7 @@ struct flowopts *flowopt_new(void) { struct flowopts *new = para_malloc(sizeof(*new)); - INIT_LIST_HEAD(&new->sockopts); + init_list_head(&new->sockopts); return new; } diff --git a/oss_write.c b/oss_write.c index 311a514d..0565167c 100644 --- a/oss_write.c +++ b/oss_write.c @@ -199,9 +199,15 @@ static int oss_post_select(__a_unused struct sched *s, void *context) if (sound_device_is_busy()) return 0; - get_btr_sample_rate(btrn, &rate); - get_btr_channels(btrn, &ch); - get_btr_sample_format(btrn, &format); + ret = get_btr_sample_rate(btrn, &rate); + if (ret < 0) + goto out; + ret = get_btr_channels(btrn, &ch); + if (ret < 0) + goto out; + ret = get_btr_sample_format(btrn, &format); + if (ret < 0) + goto out; ret = oss_init(wn, rate, ch, format); if (ret < 0) goto out; diff --git a/play.c b/play.c index 2346c6b0..14fac42f 100644 --- a/play.c +++ b/play.c @@ -109,10 +109,6 @@ typedef int (*play_cmd_handler_t)(struct lls_parse_result *lpr); struct play_command_info { play_cmd_handler_t handler; }; -#define EXPORT_PLAY_CMD_HANDLER(_cmd) \ - const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \ - .handler = com_ ## _cmd \ - }; static int loglevel = LL_WARNING; @@ -713,6 +709,11 @@ static void detach_stdout(void) btr_remove_node(&pt->btrn); } +#define EXPORT_PLAY_CMD_HANDLER(_cmd) \ + const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \ + .handler = com_ ## _cmd \ + }; + static int com_quit(__a_unused struct lls_parse_result *lpr) { pt->rq = CRT_TERM_RQ; @@ -1051,9 +1052,16 @@ static void session_open(void) history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE)); else { char *home = para_homedir(); - history_file = make_message("%s/.paraslash/play.history", - home); + char *dot_para = make_message("%s/.paraslash", home); + free(home); + ret = para_mkdir(dot_para, 0777); + /* warn, but otherwise ignore mkdir error */ + if (ret < 0 && ret != -ERRNO_TO_PARA_ERROR(EEXIST)) + PARA_WARNING_LOG("Can not create %s: %s\n", dot_para, + para_strerror(-ret)); + history_file = make_message("%s/play.history", dot_para); + free(dot_para); } ici.history_file = history_file; ici.loglevel = loglevel; @@ -1094,7 +1102,7 @@ static void session_update_time_string(char *str, unsigned len) if (btr_get_input_queue_size(pt->btrn) > 0) return; } - ie9_print_status_bar(str, len); + i9e_print_status_bar(str, len); } /* diff --git a/prebuffer_filter.c b/prebuffer_filter.c index 1988e6e0..9a801900 100644 --- a/prebuffer_filter.c +++ b/prebuffer_filter.c @@ -61,10 +61,10 @@ static int prebuffer_post_select(__a_unused struct sched *s, void *context) ret = task_get_notification(fn->task); if (ret < 0) - return ret; + goto fail; ret = btr_node_status(btrn, size, BTR_NT_INTERNAL); if (ret < 0) - return ret; + goto fail; if (ppd->barrier.tv_sec == 0) return 0; if (tv_diff(now, &ppd->barrier, NULL) < 0) @@ -73,6 +73,9 @@ static int prebuffer_post_select(__a_unused struct sched *s, void *context) return 0; btr_splice_out_node(&fn->btrn); return -E_PREBUFFER_SUCCESS; +fail: + btr_remove_node(&fn->btrn); + return ret; } static void prebuffer_open(struct filter_node *fn) diff --git a/sched.c b/sched.c index a2903940..aac8efed 100644 --- a/sched.c +++ b/sched.c @@ -244,7 +244,7 @@ struct task *task_register(struct task_info *info, struct sched *s) assert(info->post_select); if (!s->task_list.next) - INIT_LIST_HEAD(&s->task_list); + init_list_head(&s->task_list); t->info = *info; t->name = para_strdup(info->name); diff --git a/send_common.c b/send_common.c index ea494d9a..9debdfca 100644 --- a/send_common.c +++ b/send_common.c @@ -136,9 +136,9 @@ void init_sender_status(struct sender_status *ss, } ss->default_port = default_port; - INIT_LIST_HEAD(&ss->client_list); + init_list_head(&ss->client_list); /* Initialize an access control list */ - INIT_LIST_HEAD(&ss->acl); + init_list_head(&ss->acl); for (i = 0; i < lls_opt_given(acl_opt_result); i++) { const char *arg = lls_string_val(i, acl_opt_result); char addr[16]; diff --git a/server.c b/server.c index e0d50f4f..09087f7a 100644 --- a/server.c +++ b/server.c @@ -110,7 +110,7 @@ static struct signal_task *signal_task; /** The process id of the audio file selector process. */ pid_t afs_pid = 0; -/* The the main server process (parent of afs and the command handlers). */ +/* The main server process (parent of afs and the command handlers). */ static pid_t server_pid; /** @@ -298,14 +298,14 @@ static int signal_post_select(struct sched *s, __a_unused void *context) if (pid != afs_pid) continue; PARA_EMERG_LOG("fatal: afs died\n"); - kill(0, SIGTERM); - goto cleanup; + goto genocide; } break; /* die on sigint/sigterm. Kill all children too. */ case SIGINT: case SIGTERM: PARA_EMERG_LOG("terminating on signal %d\n", signum); +genocide: kill(0, SIGTERM); /* * We must wait for all of our children to die. For the afs @@ -320,7 +320,6 @@ static int signal_post_select(struct sched *s, __a_unused void *context) while (wait(NULL) != -1 || errno != ECHILD) ; /* still at least one child alive */ mutex_lock(mmd_mutex); -cleanup: free(mmd->afd.afhi.chunk_table); task_notify_all(s, E_DEADLY_SIGNAL); return -E_DEADLY_SIGNAL; diff --git a/string.h b/string.h index 10251ae7..10379a0e 100644 --- a/string.h +++ b/string.h @@ -67,7 +67,7 @@ int for_each_line(unsigned flags, char *buf, size_t size, } \ ) -__must_check __malloc void *para_realloc(void *p, size_t size); +__must_check 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); __must_check __malloc char *para_strdup(const char *s); diff --git a/sync_filter.c b/sync_filter.c index 2ca2a657..8e9ff2c5 100644 --- a/sync_filter.c +++ b/sync_filter.c @@ -103,7 +103,7 @@ static void sync_open(struct filter_node *fn) const struct lls_opt_result *r_b; ctx = fn->private_data = para_calloc(sizeof(*ctx)); - INIT_LIST_HEAD(&ctx->buddies); + init_list_head(&ctx->buddies); /* create socket to listen for incoming packets */ ret = makesock( @@ -365,7 +365,8 @@ success: ret = -E_SYNC_COMPLETE; /* success */ goto out; fail: - PARA_WARNING_LOG("%s\n", para_strerror(-ret)); + if (ret != -E_BTR_EOF) + PARA_WARNING_LOG("%s\n", para_strerror(-ret)); out: sync_close_buddies(ctx); btr_splice_out_node(&fn->btrn); diff --git a/udp_send.c b/udp_send.c index 04e2982f..91550aa8 100644 --- a/udp_send.c +++ b/udp_send.c @@ -56,6 +56,8 @@ static void udp_close_target(struct sender_client *sc) size_t len; struct udp_target *ut = sc->private_data; + if (process_is_command_handler()) + return; if (ut->sent_fec_eof) return; PARA_NOTICE_LOG("sending FEC EOF\n"); @@ -392,7 +394,7 @@ static void udp_init_target_list(void) struct sender_command_data scd; int i; - INIT_LIST_HEAD(&targets); + init_list_head(&targets); for (i = 0; i < OPT_GIVEN(UDP_TARGET); i++) { const char *arg = lls_string_val(i, OPT_RESULT(UDP_TARGET)); if (udp_resolve_target(arg, &scd) < 0) @@ -425,7 +427,7 @@ static char *udp_help(void) /* Initialize the list of udp targets. */ static void udp_send_init(void) { - INIT_LIST_HEAD(&targets); + init_list_head(&targets); sender_status = SENDER_off; udp_init_target_list(); if (!OPT_GIVEN(UDP_NO_AUTOSTART)) diff --git a/vss.c b/vss.c index 9857d92d..9e2e32ca 100644 --- a/vss.c +++ b/vss.c @@ -416,7 +416,8 @@ static int compute_group_size(struct vss_task *vsst, struct fec_group *g, g->bytes += len; g->num_chunks++; } - assert(g->num_chunks); + if (g->num_chunks == 0) + return -E_EOF; PARA_DEBUG_LOG("group #%u: %u chunks, %u bytes total\n", g->num, g->num_chunks, g->bytes); return 1; @@ -964,6 +965,11 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds) if (ret < 0) goto err; vsst->afsss = AFS_SOCKET_READY; + if (afs_code == NO_ADMISSIBLE_FILES) { + PARA_NOTICE_LOG("no admissible files\n"); + ret = 0; + goto err; + } ret = -E_NOFD; if (afs_code != NEXT_AUDIO_FILE) { PARA_ERROR_LOG("afs code: %u, expected: %d\n", afs_code, @@ -1000,9 +1006,11 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds) return; err: free(mmd->afd.afhi.chunk_table); + mmd->afd.afhi.chunk_table = NULL; if (passed_fd >= 0) close(passed_fd); - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + if (ret < 0) + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); mmd->new_vss_status_flags = VSS_NEXT; } @@ -1170,7 +1178,7 @@ void vss_init(int afs_socket, struct sched *s) vsst->afs_socket = afs_socket; ms2tv(announce_time, &vsst->announce_tv); PARA_INFO_LOG("announce timeval: %lums\n", tv2ms(&vsst->announce_tv)); - INIT_LIST_HEAD(&fec_client_list); + init_list_head(&fec_client_list); FOR_EACH_SENDER(i) { PARA_NOTICE_LOG("initializing %s sender\n", senders[i]->name); senders[i]->init(); diff --git a/web/documentation.in.html b/web/documentation.in.html index d6d690a1..e1587afb 100644 --- a/web/documentation.in.html +++ b/web/documentation.in.html @@ -23,7 +23,7 @@ [para_filter] [para_write] [para_gui] - [para_mixere] + [para_mixer] [para_play]

diff --git a/web/download.in.html b/web/download.in.html index 9ef92b7a..b2b1f5fa 100644 --- a/web/download.in.html +++ b/web/download.in.html @@ -19,7 +19,7 @@ provided at this point. There are several ways to download the source: checkout of any of the four integration branches maint, master, next, pu (see the - Git branches + Git branches section of the manual). All previous releases correspond to tagged commits and may be checked out diff --git a/web/manual.md b/web/manual.md index 44799e91..d73263b3 100644 --- a/web/manual.md +++ b/web/manual.md @@ -293,7 +293,7 @@ Requirements cd osl && make && sudo make install && sudo ldconfig sudo apt-get install autoconf libssl-dev m4 \ libmad0-dev libid3tag0-dev libasound2-dev libvorbis-dev \ - libfaad-dev libspeex-dev libFLAC-dev libsamplerate-dev realpath \ + libfaad-dev libspeex-dev libflac-dev libsamplerate-dev \ libasound2-dev libao-dev libreadline-dev libncurses-dev \ libopus-dev @@ -358,7 +358,7 @@ recognized. The mp3 tagger also needs this library for modifying you need libogg, libvorbis, libvorbisfile. The corresponding Debian packages are called `libogg-dev` and `libvorbis-dev`. -- [libfaad and mp4ff](http://www.audiocoding.com/). For aac files +- [libfaad and mp4ff](https://sourceforge.net/projects/faac/). For aac files (m4a) you need libfaad and libmp4ff (package: `libfaad-dev`). Note that for some distributions, e.g. Ubuntu, mp4ff is not part of the libfaad package. Install the faad library from sources (available @@ -1023,6 +1023,7 @@ Keyword | Type | Semantic value `bitrate` | integer | The average bitrate `frequency` | integer | The output sample rate `channels` | integer | The number of channels +`duration` | integer | The number of milliseconds `is_set("foo")` | boolean | True if attribute "foo" is set. [\*] For most audio formats, the year tag is stored as a string. It diff --git a/write.h b/write.h index cb0beff8..833cb69a 100644 --- a/write.h +++ b/write.h @@ -66,7 +66,7 @@ const struct writer *writer_get(int wid); const char *writer_name(int wid); void register_writer_node(struct writer_node *wn, struct btr_node *parent, struct sched *s); -void get_btr_sample_rate(struct btr_node *btrn, int32_t *result); -void get_btr_channels(struct btr_node *btrn, int32_t *result); -void get_btr_sample_format(struct btr_node *btrn, int32_t *result); +int get_btr_sample_rate(struct btr_node *btrn, int32_t *result); +int get_btr_channels(struct btr_node *btrn, int32_t *result); +int get_btr_sample_format(struct btr_node *btrn, int32_t *result); void print_writer_helps(bool detailed); diff --git a/write_common.c b/write_common.c index 14cc98a4..41c3eb23 100644 --- a/write_common.c +++ b/write_common.c @@ -174,24 +174,24 @@ void print_writer_helps(bool detailed) } } -static void get_btr_value(struct btr_node *btrn, const char *cmd, +static int get_btr_value(struct btr_node *btrn, const char *cmd, int32_t *result) { char *buf = NULL; int ret = btr_exec_up(btrn, cmd, &buf); - if (ret < 0) { - /* - * This really should not happen. It means one of our parent - * nodes died unexpectedly. Proceed with fingers crossed. - */ - PARA_CRIT_LOG("cmd %s: %s\n", cmd, para_strerror(-ret)); - *result = 0; - return; - } + *result = 0; + /* + * Errors may happen when the decoder returns EOF before the writer had + * a chance to query the buffer tree for the channel count, sample rate + * etc. + */ + if (ret < 0) + return ret; ret = para_atoi32(buf, result); assert(ret >= 0); free(buf); + return ret; } /** @@ -200,11 +200,11 @@ static void get_btr_value(struct btr_node *btrn, const char *cmd, * \param btrn Where to start the search. * \param result Filled in by this function. * - * This function is assumed to succeed and terminates on errors. + * \return Standard. */ -void get_btr_sample_rate(struct btr_node *btrn, int32_t *result) +int get_btr_sample_rate(struct btr_node *btrn, int32_t *result) { - get_btr_value(btrn, "sample_rate", result); + return get_btr_value(btrn, "sample_rate", result); } /** @@ -212,10 +212,12 @@ void get_btr_sample_rate(struct btr_node *btrn, int32_t *result) * * \param btrn See \ref get_btr_sample_rate. * \param result See \ref get_btr_sample_rate. + * + * \return Standard. */ -void get_btr_channels(struct btr_node *btrn, int32_t *result) +int get_btr_channels(struct btr_node *btrn, int32_t *result) { - get_btr_value(btrn, "channels", result); + return get_btr_value(btrn, "channels", result); } /** @@ -223,8 +225,10 @@ void get_btr_channels(struct btr_node *btrn, int32_t *result) * * \param btrn See \ref get_btr_sample_rate. * \param result Contains the sample format as an enum sample_format type. + * + * \return Standard. */ -void get_btr_sample_format(struct btr_node *btrn, int32_t *result) +int get_btr_sample_format(struct btr_node *btrn, int32_t *result) { - get_btr_value(btrn, "sample_format", result); + return get_btr_value(btrn, "sample_format", result); } diff --git a/yy/mp.lex b/yy/mp.lex index 2dbe21b7..4a53db27 100644 --- a/yy/mp.lex +++ b/yy/mp.lex @@ -74,6 +74,7 @@ lyrics_id {return LYRICS_ID;} bitrate {return BITRATE;} frequency {return FREQUENCY;} channels {return CHANNELS;} +duration {return DURATION;} true {return TRUE;} false {return FALSE;} diff --git a/yy/mp.y b/yy/mp.y index 0f2c9cb8..06d76101 100644 --- a/yy/mp.y +++ b/yy/mp.y @@ -210,6 +210,9 @@ static int eval_node(struct mp_ast_node *node, struct mp_context *ctx, case CHANNELS: result->intval= mp_channels(ctx); return ST_INTVAL; + case DURATION: + result->intval= mp_duration(ctx); + return ST_INTVAL; /* bools */ case IS_SET: arg = node->children[0]->sv.strval; @@ -327,6 +330,7 @@ bool mp_eval_ast(struct mp_ast_node *root, struct mp_context *ctx) %token BITRATE %token FREQUENCY %token CHANNELS +%token DURATION %token FALSE TRUE /* keywords without semantic value */ @@ -377,6 +381,7 @@ exp: NUM {$$ = $1;} | BITRATE {$$ = mp_new_ast_leaf_node(BITRATE);} | FREQUENCY {$$ = mp_new_ast_leaf_node(FREQUENCY);} | CHANNELS {$$ = mp_new_ast_leaf_node(CHANNELS);} + | DURATION {$$ = mp_new_ast_leaf_node(DURATION);} ; boolexp: IS_SET '(' STRING_LITERAL ')' {$$ = ast_node_new_unary(IS_SET, $3);}