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);}