Merge branch 'refs/heads/t/ubsan'
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 28 Nov 2021 17:10:11 +0000 (18:10 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 28 Nov 2021 17:11:17 +0000 (18:11 +0100)
A single patch which adds an option to ./configure to instruct the
compiler to activate its undefined behaviour checker.

Cooking for two months.

* refs/heads/t/ubsan:
  New configure option: --enable-ubsan.

39 files changed:
NEWS.md
afh.h
afs.c
afs.h
aft.c
alsa_write.c
ao_write.c
audiod.c
buffer_tree.c
chunk_queue.c
client_common.c
close_on_fork.c
command.c
compress_filter.c
daemon.c
error.h
interactive.c
interactive.h
list.h
mood.c
mp.c
mp.h
net.c
oss_write.c
play.c
sched.c
send_common.c
server.c
string.h
sync_filter.c
udp_send.c
vss.c
web/documentation.in.html
web/download.in.html
web/manual.md
write.h
write_common.c
yy/mp.lex
yy/mp.y

diff --git a/NEWS.md b/NEWS.md
index 3e3e7ab..292cfa4 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,60 @@
 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.
+- 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.
+
+--------------------------------------
+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/afh.h b/afh.h
index 881db3c..b3295f6 100644 (file)
--- 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 c4de2e8..3d86d19 100644 (file)
--- 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 cfa9cc6..b160649 100644 (file)
--- 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 b358c96..c8c98e7 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -1024,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
@@ -1032,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;
@@ -1042,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(&current_aft_row, &d->score);
        if (ret < 0)
@@ -1074,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) {
@@ -1090,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);
@@ -1105,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;
        /*
@@ -1116,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) {
index bc06fc3..bbbf8b6 100644 (file)
@@ -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;
        }
index 447dea8..037b929 100644 (file)
@@ -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;
index a5a7743..88599c3 100644 (file)
--- 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);
index 8a31751..f0d2002 100644 (file)
@@ -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);
index 08f57e9..cf74cc3 100644 (file)
@@ -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;
index dc24a62..c25da96 100644 (file)
@@ -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)
 {
index 28c5eab..7b464d0 100644 (file)
@@ -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;
index 0a76f68..8f5e2dd 100644 (file)
--- a/command.c
+++ b/command.c
@@ -6,10 +6,7 @@
 #include <sys/socket.h>
 #include <regex.h>
 #include <signal.h>
-#include <sys/types.h>
-#include <osl.h>
 #include <arpa/inet.h>
-#include <sys/un.h>
 #include <netdb.h>
 #include <lopsub.h>
 
@@ -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];
index 15bed6d..ff4ce6f 100644 (file)
@@ -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;
index a4e2f31..dd5420a 100644 (file)
--- 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 fe44ff5..e948212 100644 (file)
--- a/error.h
+++ b/error.h
        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"), \
  * '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
index a819730..8c4545b 100644 (file)
@@ -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;
 
index 40ff294..ddf02d7 100644 (file)
@@ -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 66c6d91..78c302f 100644 (file)
--- a/list.h
+++ b/list.h
  * 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 <stddef.h> /* 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/mood.c b/mood.c
index a63d4d2..bf3f39f 100644 (file)
--- a/mood.c
+++ b/mood.c
@@ -39,10 +39,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};
 
 /**
  * Each line of the current mood corresponds to a mood_item.
@@ -263,9 +269,9 @@ static struct mood *alloc_new_mood(const char *name)
        struct mood *m = para_calloc(sizeof(struct mood));
        if (name)
                m->name = para_strdup(name);
-       INIT_LIST_HEAD(&m->accept_list);
-       INIT_LIST_HEAD(&m->deny_list);
-       INIT_LIST_HEAD(&m->score_list);
+       init_list_head(&m->accept_list);
+       init_list_head(&m->deny_list);
+       init_list_head(&m->score_list);
        return m;
 }
 
@@ -499,20 +505,59 @@ int mood_check_callback(struct afs_callback_arg *aca)
                check_mood));
 }
 
-static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd)
-{
-       if (!n || !qd)
-               return 0;
-       return 100 * (n * x - sum) / (int64_t)int_sqrt(n) / (int64_t)int_sqrt(qd);
-}
-
+/*
+ * 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 long compute_score(struct afs_info *afsi, long mood_score)
 {
-       mood_score -= normalized_value(afsi->num_played, statistics.num,
-               statistics.num_played_sum, statistics.num_played_qd);
-       mood_score -= normalized_value(afsi->last_played, statistics.num,
-               statistics.last_played_sum, statistics.last_played_qd);
-       return mood_score / 3;
+       int64_t mean_n, mean_l,score_n, score_l;
+
+       assert(statistics.normalization_divisor > 0);
+       assert(statistics.num > 0);
+       mean_n = statistics.num_played_sum / statistics.num;
+       mean_l = statistics.last_played_sum / statistics.num;
+
+       score_n = -((int64_t)afsi->num_played - mean_n)
+               * statistics.num_played_correction
+               / statistics.normalization_divisor;
+       score_l = -((int64_t)afsi->last_played - mean_l)
+               * statistics.last_played_correction
+               / statistics.normalization_divisor;
+       return (mood_score + score_n + score_l) / 3;
 }
 
 static int add_afs_statistics(const struct osl_row *row)
@@ -556,6 +601,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;
        }
 
@@ -804,15 +850,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?
@@ -822,13 +864,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);
 }
 
 /**
@@ -841,6 +888,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;
 }
 
 /**
@@ -869,6 +935,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;
@@ -901,6 +972,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++) {
                struct admissible_file_info *a = aa.array + i;
                ret = add_to_score_table(a->aft_row, a->score);
@@ -911,7 +985,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 416b4f9..b5fa9ca 100644 (file)
--- 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.
  *
diff --git a/mp.h b/mp.h
index febbe32..891bfb0 100644 (file)
--- 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 91200fc..e1951e5 100644 (file)
--- 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;
 }
 
index 311a514..0565167 100644 (file)
@@ -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 7369c33..14fac42 100644 (file)
--- 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;
@@ -1101,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/sched.c b/sched.c
index a290394..aac8efe 100644 (file)
--- 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);
index ea494d9..9debdfc 100644 (file)
@@ -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];
index c86778c..09087f7 100644 (file)
--- a/server.c
+++ b/server.c
@@ -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;
index 10251ae..10379a0 100644 (file)
--- 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);
index 2ca2a65..8e9ff2c 100644 (file)
@@ -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);
index 04e2982..91550aa 100644 (file)
@@ -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 9857d92..9e2e32c 100644 (file)
--- 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();
index d6d690a..e1587af 100644 (file)
@@ -23,7 +23,7 @@
        [<a href="para_filter.man.html">para_filter</a>]
        [<a href="para_write.man.html">para_write</a>]
        [<a href="para_gui.man.html">para_gui</a>]
-       [<a href="para_mixer.man.html">para_mixere</a>]
+       [<a href="para_mixer.man.html">para_mixer</a>]
        [<a href="para_play.man.html">para_play</a>]
 </p>
 
index 9ef92b7..b2b1f5f 100644 (file)
@@ -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
 
-               <a href="manual.html#Git.branches">Git branches</a>
+               <a href="manual.html#Git-branches">Git branches</a>
 
                section of the manual). All previous releases
                correspond to tagged commits and may be checked out
index db28a69..d73263b 100644 (file)
@@ -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 cb0beff..833cb69 100644 (file)
--- 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);
index 14cc98a..41c3eb2 100644 (file)
@@ -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);
 }
index 2dbe21b..4a53db2 100644 (file)
--- 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 0f2c9cb..06d7610 100644 (file)
--- 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 <node> BITRATE
 %token <node> FREQUENCY
 %token <node> CHANNELS
+%token <node> DURATION
 %token <node> 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);}