From: Andre Noll
Date: Sun, 21 Aug 2022 15:16:32 +0000 (+0200)
Subject: Merge branch 'maint'
X-Git-Tag: v0.7.1~10
X-Git-Url: http://git.tuebingen.mpg.de/?a=commitdiff_plain;h=26243a4b963bc3c59020b0469f1e469025da0ad8;hp=4c1f0ea9b7b5174d86a99803c1563caf778a8ee2;p=paraslash.git
Merge branch 'maint'
Two fixes for para_play.
---
diff --git a/Makefile.in b/Makefile.in
index d4a83a77..c618561d 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -23,6 +23,7 @@ audiod_objs := @audiod_objs@
audioc_objs := @audioc_objs@
mixer_objs := @mixer_objs@
server_objs := @server_objs@
+upgrade_db_objs := @upgrade_db_objs@
write_objs := @write_objs@
afh_objs := @afh_objs@
play_objs := @play_objs@
@@ -67,4 +68,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 b88de225..6e8084d5 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 := 2021
+COPYRIGHT_YEAR := 2022
ifeq ("$(origin O)", "command line")
build_dir := $(O)
@@ -55,6 +55,7 @@ gui_objs += gui.lsg.o
play_objs += $(addsuffix _cmd.lsg.o, recv filter play write) play.lsg.o
recv_objs += recv_cmd.lsg.o recv.lsg.o
server_objs += server_cmd.lsg.o server.lsg.o
+upgrade_db_objs += upgrade_db.lsg.o
write_objs += write_cmd.lsg.o write.lsg.o
cmd_suites := $(addsuffix _cmd, audiod server play recv filter write)
@@ -77,6 +78,7 @@ audiod_objs := $(addprefix $(object_dir)/, $(audiod_objs))
audioc_objs := $(addprefix $(object_dir)/, $(audioc_objs))
mixer_objs := $(addprefix $(object_dir)/, $(mixer_objs))
server_objs := $(addprefix $(object_dir)/, $(server_objs))
+upgrade_db_objs := $(addprefix $(object_dir)/, $(upgrade_db_objs))
write_objs := $(addprefix $(object_dir)/, $(write_objs))
afh_objs := $(addprefix $(object_dir)/, $(afh_objs))
play_objs := $(addprefix $(object_dir)/, $(play_objs))
@@ -129,6 +131,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
@@ -257,7 +264,7 @@ para_recv para_afh para_play para_server: LDFLAGS += $(id3tag_ldflags)
para_write para_play para_audiod \
: LDFLAGS += $(ao_ldflags) $(pthread_ldflags)
para_client para_audioc para_play : LDFLAGS += $(readline_ldflags)
-para_server: LDFLAGS += $(osl_ldflags)
+para_server para_upgrade_db: LDFLAGS += $(osl_ldflags)
para_gui: LDFLAGS += $(curses_ldflags)
para_server \
para_client \
@@ -294,6 +301,7 @@ para_gui \
para_play \
para_recv \
para_server \
+para_upgrade_db \
para_write \
: LDFLAGS += $(lopsub_ldflags)
diff --git a/NEWS.md b/NEWS.md
index 7554cd6b..e9713d98 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,52 @@
NEWS
====
+-------------------------------------------
+0.7.1 (to be announced) "digital spindrift"
+-------------------------------------------
+
+[tarball](./releases/paraslash-git.tar.xz)
+
+----------------------------------
+0.7.0 (2022-03-12) "seismic orbit"
+----------------------------------
+
+The major incompatible change which requires to bump the major version
+is the switch from sha1 to sha256, see below for details. However,
+there are many other improvements, the usual amount of bug fixes and
+a couple of new features.
+
+- Starting with paraslash-0.7.0, the sha256 hash value of each known
+ audio file is stored in the database while older versions employed the
+ sha1 hash algorithm which has been considered insecure since 2005
+ and should no longer be used today. The switch from sha1 to sha256
+ requires users to upgrade their database using the new para_upgrade_db
+ program, followed by re-adding all files to recompute the hashes. With
+ this approach all metadata stored in the database (last played date,
+ num played value, moods, playlists, attributes etc.) are kept. An
+ simpler alternative is to start over from scratch by running the
+ "init" command but this will lose these metadata.
+- Server and client now hash the session keys with sha256 rather
+ than sha1 during the initial handshake. This feature is optional and
+ backwards compatible: old clients can still connect to a new server
+ (using sha1). Also new clients can connect to an old server (again
+ using sha1).
+- 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.
+- The fancy new logo and a minor overhaul of the web pages.
+
+Downloads:
+[tarball](./releases/paraslash-0.7.0.tar.xz),
+[signature](./releases/paraslash-0.7.0.tar.xz.asc)
+
--------------------------------------
0.6.4 (2021-11-04) "fuzzy calibration"
--------------------------------------
diff --git a/aac_afh.c b/aac_afh.c
index 2b3dd2cc..5c1225b6 100644
--- a/aac_afh.c
+++ b/aac_afh.c
@@ -35,17 +35,13 @@ struct aac_afh_context {
static uint32_t aac_afh_read_cb(void *user_data, void *dest, uint32_t want)
{
struct aac_afh_context *c = user_data;
- uint32_t have, rv;
+ size_t have, rv;
- if (want == 0 || c->fpos >= c->mapsize) {
- PARA_INFO_LOG("failed attempt to read %u bytes @%zu\n", want,
- c->fpos);
- errno = EAGAIN;
- return -1;
- }
+ if (want == 0 || c->fpos >= c->mapsize)
+ return 0;
have = c->mapsize - c->fpos;
- rv = PARA_MIN(have, want);
- PARA_DEBUG_LOG("reading %u bytes @%zu\n", rv, c->fpos);
+ rv = PARA_MIN(have, (size_t)want);
+ PARA_DEBUG_LOG("reading %zu bytes @%zu\n", rv, c->fpos);
memcpy(dest, c->map + c->fpos, rv);
c->fpos += rv;
return rv;
@@ -130,8 +126,8 @@ static void aac_afh_close(void *afh_context)
*/
int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample);
-static int aac_afh_get_chunk(long unsigned chunk_num, void *afh_context,
- const char **buf, size_t *len)
+static int aac_afh_get_chunk(uint32_t chunk_num, void *afh_context,
+ const char **buf, uint32_t *len)
{
struct aac_afh_context *c = afh_context;
int32_t ss;
@@ -170,8 +166,7 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
struct aac_afh_context *c;
int64_t tmp;
const char *buf;
- size_t sz;
- uint32_t n;
+ uint32_t n, len;
ret = aac_afh_open(map, numbytes, (void **)&c);
if (ret < 0)
@@ -196,16 +191,16 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
afhi->chunks_total = rv;
afhi->max_chunk_size = 0;
for (n = 0; n < afhi->chunks_total; n++) {
- if (aac_afh_get_chunk(n, c, &buf, &sz) < 0)
+ if (aac_afh_get_chunk(n, c, &buf, &len) < 0)
break;
- afhi->max_chunk_size = PARA_MAX((size_t)afhi->max_chunk_size, sz);
+ afhi->max_chunk_size = PARA_MAX(afhi->max_chunk_size, len);
}
tmp = c->masc.sbr_present_flag == 1? 2048 : 1024;
afhi->seconds_total = tmp * afhi->chunks_total / afhi->frequency;
ms2tv(1000 * tmp / afhi->frequency, &afhi->chunk_tv);
- if (aac_afh_get_chunk(0, c, &buf, &sz) >= 0)
+ if (aac_afh_get_chunk(0, c, &buf, &len) >= 0)
numbytes -= buf - map;
afhi->bitrate = 8 * numbytes / afhi->seconds_total / 1000;
_aac_afh_get_taginfo(c->mp4ff, &afhi->tags);
diff --git a/acl.c b/acl.c
index 2c900526..59ffab3e 100644
--- a/acl.c
+++ b/acl.c
@@ -101,7 +101,7 @@ static void acl_del_entry(struct list_head *acl, char *addr, unsigned netmask)
struct access_info *ai, *tmp;
struct in_addr to_delete;
- PARA_NOTICE_LOG("removing entries matching %s/%u\n", addr, netmask);
+ PARA_INFO_LOG("removing entries matching %s/%u\n", addr, netmask);
inet_pton(AF_INET, addr, &to_delete);
list_for_each_entry_safe(ai, tmp, acl, node) {
@@ -111,7 +111,7 @@ static void acl_del_entry(struct list_head *acl, char *addr, unsigned netmask)
const char *p = inet_ntop(AF_INET, &ai->addr.s_addr,
dst, sizeof(dst));
if (p)
- PARA_INFO_LOG("removing %s/%u\n", p,
+ PARA_DEBUG_LOG("removing %s/%u\n", p,
ai->netmask);
list_del(&ai->node);
free(ai);
diff --git a/afh.c b/afh.c
index c896a7d1..e419d270 100644
--- a/afh.c
+++ b/afh.c
@@ -159,7 +159,7 @@ static void print_chunk_table(struct afh_info *afhi, int audio_format_id,
struct timeval tv;
long unsigned from, to;
const char *buf;
- size_t len;
+ uint32_t len;
tv_scale(i, &afhi->chunk_tv, &tv);
from = tv2ms(&tv);
tv_scale(i + 1, &afhi->chunk_tv, &tv);
@@ -177,7 +177,7 @@ static void print_chunk_table(struct afh_info *afhi, int audio_format_id,
printf("%td - %td", buf - (const char *)map,
buf + len - (const char *)map);
if (!OPT_GIVEN(PARSER_FRIENDLY))
- printf(" (%zu)", len);
+ printf(" (%u)", len);
printf("\n");
}
afh_close(ctx, audio_format_id);
diff --git a/afh.h b/afh.h
index b3295f6e..ba72d80e 100644
--- a/afh.h
+++ b/afh.h
@@ -111,8 +111,8 @@ struct audio_format_handler {
* portion of the memory mapped audio file. The caller must not call
* free() on it.
*/
- int (*get_chunk)(long unsigned chunk_num, void *afh_context,
- const char **buf, size_t *len);
+ int (*get_chunk)(uint32_t chunk_num, void *afh_context,
+ const char **buf, uint32_t *len);
/** Deallocate the resources occupied by ->open(). */
void (*close)(void *afh_context);
/**
@@ -131,7 +131,7 @@ int compute_afhi(const char *path, char *data, size_t size,
const char *audio_format_name(int);
__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
uint8_t audio_format_id, const void *map, size_t mapsize,
- const char **buf, size_t *len, void **afh_context);
+ const char **buf, uint32_t *len, void **afh_context);
void afh_close(void *afh_context, uint8_t audio_format_id);
int32_t afh_get_start_chunk(int32_t approx_chunk_num,
const struct afh_info *afhi, uint8_t audio_format_id);
diff --git a/afh_common.c b/afh_common.c
index a267f58b..7e8f63d2 100644
--- a/afh_common.c
+++ b/afh_common.c
@@ -219,7 +219,7 @@ void clear_afhi(struct afh_info *afhi)
free(afhi->tags.comment);
}
-static inline size_t get_chunk_len(long unsigned chunk_num,
+static inline uint32_t get_chunk_len(long unsigned chunk_num,
const struct afh_info *afhi)
{
return afhi->chunk_table[chunk_num + 1] - afhi->chunk_table[chunk_num];
@@ -247,7 +247,7 @@ static inline size_t get_chunk_len(long unsigned chunk_num,
*/
__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
uint8_t audio_format_id, const void *map, size_t mapsize,
- const char **buf, size_t *len, void **afh_context)
+ const char **buf, uint32_t *len, void **afh_context)
{
struct audio_format_handler *afh = afl[audio_format_id];
diff --git a/afh_recv.c b/afh_recv.c
index 4f8ff497..6a0ec239 100644
--- a/afh_recv.c
+++ b/afh_recv.c
@@ -174,6 +174,7 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
char *buf;
const char *start;
size_t size;
+ uint32_t len;
struct timeval chunk_time;
unsigned j_given = RECV_CMD_OPT_GIVEN(AFH, JUST_IN_TIME, lpr);
unsigned H_given = RECV_CMD_OPT_GIVEN(AFH, NO_HEADER, lpr);
@@ -197,12 +198,12 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
long unsigned n;
for (n = pard->first_chunk; n < pard->last_chunk; n++) {
ret = afh_get_chunk(n, afhi, pard->audio_format_num,
- pard->map, pard->map_size, &start, &size,
+ pard->map, pard->map_size, &start, &len,
&pard->afh_context);
if (ret < 0)
goto out;
- PARA_DEBUG_LOG("adding %zu bytes\n", size);
- btr_add_output_dont_free(start, size, btrn);
+ PARA_DEBUG_LOG("adding %u bytes\n", len);
+ btr_add_output_dont_free(start, len, btrn);
}
ret = -E_RECV_EOF;
goto out;
@@ -218,12 +219,12 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
}
ret = afh_get_chunk(pard->current_chunk, afhi,
pard->audio_format_num, pard->map,
- pard->map_size, &start, &size,
+ pard->map_size, &start, &len,
&pard->afh_context);
if (ret < 0)
goto out;
PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk);
- btr_add_output_dont_free(start, size, btrn);
+ btr_add_output_dont_free(start, len, btrn);
if (pard->current_chunk >= pard->last_chunk) {
ret = -E_RECV_EOF;
goto out;
diff --git a/afs.c b/afs.c
index 23ba2ad6..71067025 100644
--- a/afs.c
+++ b/afs.c
@@ -39,11 +39,10 @@ enum afs_table_num {
TBLNUM_AUDIO_FILES,
/** The table for the paraslash attributes. See \ref attribute.c. */
TBLNUM_ATTRIBUTES,
- /**
- * Paraslash's scoring system is based on Gaussian normal
- * distributions, and the relevant data is stored in the rbtrees of an
- * osl table containing only volatile columns. See \ref score.c for
- * details.
+ /*
+ * Moods and playlists organize the current set of admissible files in
+ * an osl table which contains only volatile columns. Each row consists
+ * of a pointer to an audio file and the score value of this file.
*/
TBLNUM_SCORES,
/**
@@ -91,26 +90,26 @@ static char *current_mop; /* mode or playlist specifier. NULL means dummy mood *
extern uint32_t afs_socket_cookie;
/**
- * Struct to let command handlers execute a callback in afs context.
- *
- * Commands that need to change the state of afs can't change the relevant data
- * structures directly because commands are executed in a child process, i.e.
- * they get their own virtual address space.
+ * Passed from command handlers to afs.
*
- * This structure is used by \p send_callback_request() (executed from handler
- * context) in order to let the afs process call the specified function. An
- * instance of that structure is written to a shared memory area together with
- * the arguments to the callback function. The identifier of the shared memory
- * area is written to the command socket.
+ * Command handlers cannot change the afs database directly because they run in
+ * a separate process. The callback query structure circumvents this
+ * restriction as follows. To instruct the afs process to execute a particular
+ * function, the command hander writes an instance of this structure to a
+ * shared memory area, along with the arguments to the callback function. The
+ * identifier of the shared memory area is transferred to the afs process via
+ * the command socket.
*
- * The afs process accepts connections on the command socket and reads the
- * shared memory id, attaches the corresponding area, calls the given handler to
- * perform the desired action and to optionally compute a result.
+ * The afs process reads the shared memory id from the command socket, attaches
+ * the corresponding area, and calls the callback function whose address is
+ * stored in the area.
*
- * The result and a \p callback_result structure is then written to another
- * shared memory area. The identifier for that area is written to the handler's
- * command socket, so that the handler process can read the id, attach the
- * shared memory area and use the result.
+ * The command output, if any, is transferred back to the command handler in
+ * the same way: The afs process writes the output to a second shared memory
+ * area together with a fixed size metadata header whose format corresponds to
+ * the \ref callback_result structure. The identifier of this area is sent back
+ * to the command handler which attaches the area and forwards the output to
+ * the remote client.
*
* \sa \ref struct callback_result.
*/
@@ -482,6 +481,12 @@ static int activate_mood_or_playlist(const char *arg, int *num_admissible,
if (num_admissible)
*num_admissible = ret;
current_play_mode = mode;
+ /*
+ * We get called with arg == current_mop from the signal dispatcher
+ * after SIGHUP and from the error path of the select command to
+ * re-select the current mood or playlist. In this case the assignment
+ * to current_mop below would result in a use-after-free condition.
+ */
if (arg != current_mop) {
free(current_mop);
if (arg) {
@@ -648,7 +653,7 @@ static char *database_dir;
static void close_afs_tables(void)
{
int i;
- PARA_NOTICE_LOG("closing afs_tables\n");
+ PARA_NOTICE_LOG("closing afs tables\n");
for (i = 0; i < NUM_AFS_TABLES; i++)
afs_tables[i].close();
free(database_dir);
@@ -663,7 +668,7 @@ static void get_database_dir(void)
else {
char *home = para_homedir();
database_dir = make_message(
- "%s/.paraslash/afs_database-0.4", home);
+ "%s/.paraslash/afs_database-0.7", home);
free(home);
}
}
@@ -985,7 +990,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/aft.c b/aft.c
index c8c98e7a..e0bde7fc 100644
--- a/aft.c
+++ b/aft.c
@@ -42,7 +42,7 @@ struct ls_data {
*/
static struct osl_table *audio_file_table; /* NULL if table not open */
static struct osl_row *current_aft_row; /* NULL if no audio file open */
-static unsigned char current_hash[HASH_SIZE]; /* only used on sighup */
+static unsigned char current_hash[HASH2_SIZE]; /* only used on sighup */
static char *status_items;
static char *parser_friendly_status_items;
@@ -226,7 +226,7 @@ static struct osl_column_description aft_cols[] = {
.storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
.name = "hash",
.compare_function = aft_hash_compare,
- .data_size = HASH_SIZE
+ .data_size = HASH2_SIZE
},
[AFTCOL_PATH] = {
.storage_type = OSL_MAPPED_STORAGE,
@@ -251,7 +251,7 @@ static struct osl_column_description aft_cols[] = {
};
static struct osl_table_description audio_file_table_desc = {
- .name = "audio_files",
+ .name = "audio-files",
.num_columns = NUM_AFT_COLUMNS,
.flags = OSL_LARGE_TABLE,
.column_descriptions = aft_cols
@@ -442,7 +442,7 @@ int aft_get_row_of_path(const char *path, struct osl_row **row)
*/
static int aft_get_row_of_hash(unsigned char *hash, struct osl_row **row)
{
- const struct osl_object obj = {.data = hash, .size = HASH_SIZE};
+ const struct osl_object obj = {.data = hash, .size = HASH2_SIZE};
return osl(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row));
}
@@ -779,18 +779,17 @@ static void write_image_items(struct para_buffer *b, struct afs_info *afsi)
static void write_filename_items(struct para_buffer *b, const char *path,
bool basename)
{
- char *val;
+ const char *slash;
if (basename) {
WRITE_STATUS_ITEM(b, SI_basename, "%s\n", path);
return;
}
WRITE_STATUS_ITEM(b, SI_path, "%s\n", path);
- val = para_basename(path);
- WRITE_STATUS_ITEM(b, SI_basename, "%s\n", val? val : "");
- val = para_dirname(path);
- WRITE_STATUS_ITEM(b, SI_directory, "%s\n", val? val : "");
- free(val);
+ slash = strrchr(path, '/');
+ WRITE_STATUS_ITEM(b, SI_basename, "%s\n", slash? slash + 1 : path);
+ WRITE_STATUS_ITEM(b, SI_directory, "%.*s\n",
+ slash? (int)(slash - path) : (int)strlen(path), path);
}
static int print_chunk_table(struct ls_data *d, struct para_buffer *b)
@@ -838,7 +837,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
char duration_buf[30]; /* nobody has an audio file long enough to overflow this */
struct afs_info *afsi = &d->afsi;
struct afh_info *afhi = &d->afhi;
- char asc_hash[2 * HASH_SIZE + 1];
+ char asc_hash[2 * HASH2_SIZE + 1];
if (opts->mode == LS_MODE_SHORT) {
para_printf(b, "%s\n", d->path);
@@ -897,13 +896,13 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
goto out;
}
if (opts->mode == LS_MODE_MBOX) {
- const char *bn = para_basename(d->path);
+ const char *slash = strrchr(d->path, '/');
para_printf(b,
"From foo@localhost %s\n"
"Received: from\nTo: bar\nFrom: a\n"
"Subject: %s\n\n",
last_played_time,
- bn? bn : "?");
+ slash? slash + 1 : "?");
}
write_filename_items(b, d->path, lls_opt_given(r_b));
if (lls_opt_given(r_a))
@@ -1041,7 +1040,7 @@ out:
*/
int open_and_update_audio_file(int *fd)
{
- unsigned char file_hash[HASH_SIZE];
+ unsigned char file_hash[HASH2_SIZE];
struct osl_object afsi_obj;
struct afs_info new_afsi;
int ret;
@@ -1101,7 +1100,7 @@ again:
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);
+ hash2_function(map.data, map.size, file_hash);
ret = hash_compare(file_hash, d->hash);
para_munmap(map.data, map.size);
if (ret) {
@@ -1141,7 +1140,7 @@ out:
static int ls_hash_compare(const void *a, const void *b)
{
struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
- return memcmp(d1->hash, d2->hash, HASH_SIZE);
+ return memcmp(d1->hash, d2->hash, HASH2_SIZE);
}
static int ls_audio_format_compare(const void *a, const void *b)
@@ -1448,7 +1447,7 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr)
else if (!strcmp(val, "p") || !strcmp(val, "parser-friendly"))
opts->mode = LS_MODE_PARSER;
else {
- ret = -E_AFT_SYNTAX;
+ ret = -ERRNO_TO_PARA_ERROR(EINVAL);
goto out;
}
}
@@ -1479,7 +1478,7 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr)
else if (!strcmp(val, "h") || !strcmp(val, "hash"))
opts->sorting = LS_SORT_BY_HASH;
else {
- ret = -E_AFT_SYNTAX;
+ ret = -ERRNO_TO_PARA_ERROR(EINVAL);
goto out;
}
}
@@ -1536,7 +1535,7 @@ enum com_add_buffer_offsets {
/** The hash of the audio file being added. */
CAB_HASH_OFFSET = 13,
/** Start of the path of the audio file. */
- CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH_SIZE),
+ CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH2_SIZE),
};
/*
@@ -1559,7 +1558,7 @@ static void save_add_callback_buffer(unsigned char *hash, const char *path,
assert(size <= ~(uint32_t)0);
write_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET, audio_format_num);
- memcpy(buf + CAB_HASH_OFFSET, hash, HASH_SIZE);
+ memcpy(buf + CAB_HASH_OFFSET, hash, HASH2_SIZE);
strcpy(buf + CAB_PATH_OFFSET, path);
pos = CAB_PATH_OFFSET + path_len;
write_u32(buf + CAB_AFHI_OFFSET_POS, pos);
@@ -1635,7 +1634,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
struct osl_row *hs;
struct osl_object objs[NUM_AFT_COLUMNS];
unsigned char *hash;
- char asc[2 * HASH_SIZE + 1];
+ char asc[2 * HASH2_SIZE + 1];
int ret;
char afsi_buf[AFSI_SIZE];
char *slpr = buf + read_u32(buf + CAB_LPR_OFFSET);
@@ -1652,7 +1651,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
hash = (unsigned char *)buf + CAB_HASH_OFFSET;
hash_to_asc(hash, asc);
objs[AFTCOL_HASH].data = buf + CAB_HASH_OFFSET;
- objs[AFTCOL_HASH].size = HASH_SIZE;
+ objs[AFTCOL_HASH].size = HASH2_SIZE;
path = buf + CAB_PATH_OFFSET;
objs[AFTCOL_PATH].data = path;
@@ -1715,7 +1714,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
objs[AFTCOL_CHUNKS].data = buf + chunks_offset;
objs[AFTCOL_CHUNKS].size = aca->query.size - chunks_offset;
if (pb && !hs) { /* update pb's hash */
- char old_asc[2 * HASH_SIZE + 1];
+ char old_asc[2 * HASH2_SIZE + 1];
unsigned char *old_hash;
ret = get_hash_of_row(pb, &old_hash);
if (ret < 0)
@@ -1820,7 +1819,7 @@ static int add_one_audio_file(const char *path, void *private_data)
struct afh_info afhi, *afhi_ptr = NULL;
struct osl_row *pb = NULL, *hs = NULL; /* path brother/hash sister */
struct osl_object map, obj = {.data = NULL}, query;
- unsigned char hash[HASH_SIZE];
+ unsigned char hash[HASH2_SIZE];
bool a_given = SERVER_CMD_OPT_GIVEN(ADD, ALL, pad->lpr);
bool f_given = SERVER_CMD_OPT_GIVEN(ADD, FORCE, pad->lpr);
bool l_given = SERVER_CMD_OPT_GIVEN(ADD, LAZY, pad->lpr);
@@ -1848,11 +1847,11 @@ static int add_one_audio_file(const char *path, void *private_data)
ret = mmap_full_file(path, O_RDONLY, &map.data, &map.size, &fd);
if (ret < 0)
goto out_free;
- hash_function(map.data, map.size, hash);
+ hash2_function(map.data, map.size, hash);
/* Check whether the database contains a file with the same hash. */
query.data = hash;
- query.size = HASH_SIZE;
+ query.size = HASH2_SIZE;
ret = send_callback_request(hash_sister_callback, &query,
get_row_pointer_from_result, &hs);
if (ret < 0)
@@ -2517,7 +2516,7 @@ static void aft_close(void)
PARA_WARNING_LOG("hash lookup failure\n");
current_aft_row = NULL;
} else
- memcpy(current_hash, p, HASH_SIZE);
+ memcpy(current_hash, p, HASH2_SIZE);
}
osl_close_table(audio_file_table, OSL_MARK_CLEAN);
audio_file_table = NULL;
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/attribute.c b/attribute.c
index 414a65e6..fb1b3eac 100644
--- a/attribute.c
+++ b/attribute.c
@@ -104,7 +104,7 @@ int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum)
return 1;
}
-/** Data passed to the action function of lsatt */
+/* Data passed to the action function of lsatt */
static int print_attribute(struct osl_table *table, struct osl_row *row,
const char *name, void *data)
{
@@ -470,26 +470,12 @@ int attribute_check_callback(struct afs_callback_arg *aca)
return aft_check_attributes(att_mask, &aca->pbout);
}
-/**
- * Close the attribute table.
- *
- * \sa \ref osl_close_table().
- */
static void attribute_close(void)
{
osl_close_table(attribute_table, OSL_MARK_CLEAN);
attribute_table = NULL;
}
-/**
- * Open the attribute table.
- *
- * \param dir The database directory.
- *
- * \return Positive on success, negative on errors.
- *
- * \sa \ref osl_open_table().
- */
static int attribute_open(const char *dir)
{
int ret;
diff --git a/audiod.c b/audiod.c
index 88599c3f..119adbc0 100644
--- a/audiod.c
+++ b/audiod.c
@@ -445,7 +445,8 @@ static void close_receiver(int slot_num)
task_reap(&s->receiver_node->task);
free(s->receiver_node);
s->receiver_node = NULL;
- stat_task->current_audio_format_num = -1;
+ if (audiod_status == AUDIOD_ON)
+ stat_task->current_audio_format_num = -1;
tv_add(now, &(struct timeval)EMBRACE(0, 200 * 1000),
&a->restart_barrier);
}
@@ -479,6 +480,23 @@ static void close_writers(struct slot_info *s)
s->wns = NULL;
}
+static void notify_writers(int error)
+{
+ int i;
+
+ FOR_EACH_SLOT(i) {
+ struct slot_info *s = slot + i;
+ struct audio_format_info *a;
+ int j;
+
+ if (s->format < 0)
+ continue;
+ a = afi + s->format;
+ for (j = 0; j < a->num_writers; j++)
+ task_notify(s->wns[j].task, error);
+ }
+}
+
static void close_filters(struct slot_info *s)
{
int i;
@@ -731,8 +749,8 @@ static void compute_time_diff(const struct timeval *status_time)
if (count > 5) {
int s = tv_diff(&diff, &stat_task->sa_time_diff, &tmp);
if (tv_diff(&max_deviation, &tmp, NULL) < 0)
- PARA_WARNING_LOG("time diff jump: %lums\n",
- s * tv2ms(&tmp));
+ PARA_WARNING_LOG("time diff jump: %c%lums\n",
+ s < 0? '-' : '+', tv2ms(&tmp));
}
count++;
sa_time_diff_sign = tv_convex_combination(
@@ -1050,7 +1068,7 @@ static int signal_post_select(struct sched *s, void *context)
case SIGINT:
case SIGTERM:
case SIGHUP:
- PARA_NOTICE_LOG("received signal %d\n", signum);
+ PARA_WARNING_LOG("terminating on signal %d\n", signum);
task_notify_all(s, E_AUDIOD_SIGNAL);
return -E_AUDIOD_SIGNAL;
}
@@ -1076,7 +1094,7 @@ static int command_post_select(struct sched *s, void *context)
return ret;
ret = handle_connect(ct->fd, &s->rfds);
if (ret < 0) {
- PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
if (ret == -E_AUDIOD_TERM) {
task_notify_all(s, -ret);
return ret;
@@ -1217,8 +1235,9 @@ static void start_stop_decoders(void)
struct slot_info *sl;
close_unused_slots();
- if (audiod_status != AUDIOD_ON ||
- !(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING))
+ if (audiod_status != AUDIOD_ON)
+ return notify_writers(E_NOT_PLAYING);
+ if (!(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING))
return notify_receivers(E_NOT_PLAYING);
if (!must_start_decoder())
return;
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 3edaab5d..8caf4483 100644
--- a/client.c
+++ b/client.c
@@ -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 c25da96b..f8ee80c9 100644
--- a/client_common.c
+++ b/client_common.c
@@ -258,6 +258,11 @@ static int send_sb_command(struct client_task *ct)
return send_sb(ct, 0, command, len, SBD_COMMAND, false);
}
+static bool has_feature(const char *feature, struct client_task *ct)
+{
+ return find_arg(feature, ct->features) >= 0? true : false;
+}
+
/*
* This function reads or writes to the socket file descriptor which
* corresponds to an established connection between the client and the server.
@@ -290,16 +295,25 @@ static int client_post_select(struct sched *s, void *context)
ct->status = CL_RECEIVED_WELCOME;
return 0;
case CL_RECEIVED_WELCOME: /* send auth command */
+ {
+ /*
+ * Use sha256 iff the server announced the feature. After 0.7.0
+ * we may request and use the feature unconditionally. After
+ * 0.8.0 we no longer need to request the feature.
+ */
+ bool has_sha256;
if (!FD_ISSET(ct->scc.fd, &s->wfds))
return 0;
- sprintf(buf, AUTH_REQUEST_MSG "%s sideband,aes_ctr128",
- ct->user);
+ has_sha256 = has_feature("sha256", ct);
+ sprintf(buf, AUTH_REQUEST_MSG "%s%s", ct->user, has_sha256?
+ " sha256" : "");
PARA_INFO_LOG("--> %s\n", buf);
ret = write_buffer(ct->scc.fd, buf);
if (ret < 0)
goto out;
ct->status = CL_SENT_AUTH;
return 0;
+ }
case CL_SENT_AUTH:
/*
* Receive challenge and session keys, decrypt the challenge and
@@ -325,19 +339,29 @@ static int client_post_select(struct sched *s, void *context)
free(sbb.iov.iov_base);
if (ret < 0)
goto out;
- ct->challenge_hash = para_malloc(HASH_SIZE);
- hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
+ ct->challenge_hash = para_malloc(HASH2_SIZE);
+
+ if (has_feature("sha256", ct)) {
+ hash2_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
+ hash2_to_asc(ct->challenge_hash, buf);
+ } else {
+ hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
+ hash_to_asc(ct->challenge_hash, buf);
+ }
ct->scc.send = sc_new(crypt_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN);
ct->scc.recv = sc_new(crypt_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN,
SESSION_KEY_LEN);
- hash_to_asc(ct->challenge_hash, buf);
PARA_INFO_LOG("--> %s\n", buf);
ct->status = CL_RECEIVED_CHALLENGE;
return 0;
}
case CL_RECEIVED_CHALLENGE:
- ret = send_sb(ct, 0, ct->challenge_hash, HASH_SIZE,
- SBD_CHALLENGE_RESPONSE, false);
+ if (has_feature("sha256", ct))
+ ret = send_sb(ct, 0, ct->challenge_hash, HASH2_SIZE,
+ SBD_CHALLENGE_RESPONSE, false);
+ else
+ ret = send_sb(ct, 0, ct->challenge_hash, HASH_SIZE,
+ SBD_CHALLENGE_RESPONSE, false);
if (ret != 0)
ct->challenge_hash = NULL;
if (ret <= 0)
@@ -428,8 +452,7 @@ out:
return 0;
btr_remove_node(&ct->btrn[0]);
btr_remove_node(&ct->btrn[1]);
- if (ret != -E_SERVER_CMD_SUCCESS && ret != -E_SERVER_CMD_FAILURE)
- PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ PARA_NOTICE_LOG("closing connection (%s)\n", para_strerror(-ret));
if (ct->scc.fd >= 0) {
close(ct->scc.fd);
ct->scc.fd = -1;
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 7b3d6faf..e3f12931 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"
@@ -745,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);
@@ -762,7 +750,7 @@ static void reset_signals(void)
}
struct connection_features {
- int dummy; /* none at the moment */
+ bool sha256_requested; /* can be removed after 0.7.0 */
};
static int parse_auth_request(char *buf, int len, const struct user **u,
@@ -787,11 +775,22 @@ static int parse_auth_request(char *buf, int len, const struct user **u,
*p = '\0';
p++;
create_argv(p, ",", &features);
+ /*
+ * Still accept sideband and AES feature requests (as a no-op)
+ * because some 0.6.x clients request them. The two checks
+ * below may be removed after 0.7.1.
+ */
for (i = 0; features[i]; i++) {
if (strcmp(features[i], "sideband") == 0)
continue;
if (strcmp(features[i], "aes_ctr128") == 0)
continue;
+ /*
+ * ->sha256_requested can go away after 0.7.0 but the
+ * check has to stay until 0.9.0.
+ */
+ if (strcmp(features[i], "sha256") == 0)
+ cf->sha256_requested = true;
else {
ret = -E_BAD_FEATURE;
goto out;
@@ -829,7 +828,7 @@ static int run_command(struct command_context *cc, struct iovec *iov)
}
perms = server_command_perms[ret];
if ((perms & cc->u->perms) != perms)
- return -E_PERM;
+ return -ERRNO_TO_PARA_ERROR(EPERM);
lcmd = lls_cmd(ret, server_cmd_suite);
end = iov->iov_base + iov->iov_len;
for (i = 0; p < end; i++)
@@ -889,7 +888,7 @@ int handle_connect(int fd)
{
int ret;
unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
- unsigned char challenge_hash[HASH_SIZE];
+ unsigned char challenge_hash[HASH2_SIZE];
char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
size_t numbytes;
struct command_context cc_struct = {.u = NULL}, *cc = &cc_struct;
@@ -905,7 +904,7 @@ int handle_connect(int fd)
/* send Welcome message */
ret = write_va_buffer(fd, "This is para_server, version "
PACKAGE_VERSION ".\n"
- "Features: sideband,aes_ctr128\n"
+ "Features: sha256\n" /* no longer announce this after 0.8.0 */
);
if (ret < 0)
goto net_err;
@@ -953,11 +952,19 @@ int handle_connect(int fd)
* of the random data.
*/
ret = -E_BAD_AUTH;
- if (numbytes != HASH_SIZE)
- goto net_err;
- hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
- if (memcmp(challenge_hash, buf, HASH_SIZE))
- goto net_err;
+ if (cf.sha256_requested) {
+ if (numbytes != HASH2_SIZE)
+ goto net_err;
+ hash2_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
+ if (memcmp(challenge_hash, buf, HASH2_SIZE))
+ goto net_err;
+ } else { /* old client. This can be removed after 0.7.0 */
+ if (numbytes != HASH_SIZE)
+ goto net_err;
+ hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
+ if (memcmp(challenge_hash, buf, HASH_SIZE))
+ goto net_err;
+ }
/* auth successful */
alarm(0);
PARA_INFO_LOG("good auth for %s\n", cc->u->name);
diff --git a/configure.ac b/configure.ac
index f518f89a..b817979f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -373,11 +373,16 @@ 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
build_server="yes"
- executables="$executables server"
+ executables="$executables server upgrade_db"
server_errlist_objs="
server
afh_common
@@ -391,7 +396,6 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
daemon
http_send
close_on_fork
- mm
crypt_common
base64
ipc
@@ -437,6 +441,17 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
else
build_server="no"
fi
+############################################################# upgrade_db
+upgrade_db_objs='
+ crypt_common
+ exec
+ fd
+ string
+ upgrade_db
+ version
+ base64
+'
+AC_SUBST(upgrade_db_objs, add_dot_o($upgrade_db_objs))
############################################################# client
if test -n "$CRYPTOLIB"; then
build_client="yes"
@@ -855,6 +870,7 @@ id3 version 2 support: $HAVE_ID3TAG
faad: $HAVE_FAAD
audio format handlers: $audio_format_handlers
+exe: $executables
para_server: $build_server
para_gui: $build_gui
para_mixer: $build_mixer
diff --git a/crypt.h b/crypt.h
index 9623a003..5ca6a541 100644
--- a/crypt.h
+++ b/crypt.h
@@ -200,3 +200,42 @@ void hash_to_asc(const unsigned char *hash, char *asc);
* less than or equal to h2, respectively.
*/
int hash_compare(const unsigned char *h1, const unsigned char *h2);
+
+/** Size of the hash value in bytes. */
+#define HASH2_SIZE 32
+
+/**
+ * Compute the hash2 of the given input data.
+ *
+ * \param data Pointer to the data to compute the hash value from.
+ * \param len The length of \a data in bytes.
+ * \param hash Result pointer.
+ *
+ * \a hash must point to an area at least \p HASH2_SIZE bytes large.
+ *
+ * \sa sha(3), openssl(1).
+ * */
+void hash2_function(const char *data, unsigned long len, unsigned char *hash);
+
+/**
+ * Convert a hash2 value to ascii format.
+ *
+ * \param hash the hash value.
+ * \param asc Result pointer.
+ *
+ * \a asc must point to an area of at least 2 * \p HASH2_SIZE + 1 bytes which
+ * will be filled by the function with the ascii representation of the hash
+ * value given by \a hash, and a terminating \p NULL byte.
+ */
+void hash2_to_asc(const unsigned char *hash, char *asc);
+
+/**
+ * Compare two version 2 hashes.
+ *
+ * \param h1 Pointer to the first hash value.
+ * \param h2 Pointer to the second hash value.
+ *
+ * \return 1, -1, or zero, depending on whether \a h1 is greater than,
+ * less than or equal to h2, respectively.
+ */
+int hash2_compare(const unsigned char *h1, const unsigned char *h2);
diff --git a/crypt_common.c b/crypt_common.c
index ff24e356..3a44dbdd 100644
--- a/crypt_common.c
+++ b/crypt_common.c
@@ -160,6 +160,31 @@ int hash_compare(const unsigned char *h1, const unsigned char *h2)
return 0;
}
+void hash2_to_asc(const unsigned char *hash, char *asc)
+{
+ int i;
+ const char hexchar[] = "0123456789abcdef";
+
+ for (i = 0; i < HASH2_SIZE; i++) {
+ asc[2 * i] = hexchar[hash[i] >> 4];
+ asc[2 * i + 1] = hexchar[hash[i] & 0xf];
+ }
+ asc[2 * HASH2_SIZE] = '\0';
+}
+
+int hash2_compare(const unsigned char *h1, const unsigned char *h2)
+{
+ int i;
+
+ for (i = 0; i < HASH2_SIZE; i++) {
+ if (h1[i] < h2[i])
+ return -1;
+ if (h1[i] > h2[i])
+ return 1;
+ }
+ return 0;
+}
+
/**
* Check header of an openssh private key and compute bignum offset.
*
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..60c23294 100644
--- a/error.h
+++ b/error.h
@@ -13,7 +13,6 @@
PARA_ERROR(AFS_SHORT_READ, "short read from afs socket"), \
PARA_ERROR(AFS_SIGNAL, "afs caught deadly signal"), \
PARA_ERROR(AFS_SOCKET, "afs socket not writable"), \
- PARA_ERROR(AFT_SYNTAX, "audio file table syntax error"), \
PARA_ERROR(ALSA, "alsa error"), \
PARA_ERROR(ALSA_MIX_GET_VAL, "could not read control element state"), \
PARA_ERROR(ALSA_MIX_OPEN, "could not open mixer"), \
@@ -166,7 +165,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"), \
@@ -174,7 +172,6 @@
PARA_ERROR(OPUS_HEADER, "invalid opus header"), \
PARA_ERROR(OPUS_SET_GAIN, "opus: could not set gain"), \
PARA_ERROR(PATH_FOUND, ""), /* not really an error */ \
- PARA_ERROR(PERM, "permission denied"), \
PARA_ERROR(PLAYLIST_EMPTY, "attempted to load empty playlist"), \
PARA_ERROR(PLAYLIST_LOADED, ""), /* not really an error */ \
PARA_ERROR(PREBUFFER_SUCCESS, "prebuffering complete"), \
@@ -253,6 +250,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/gcrypt.c b/gcrypt.c
index dbe49008..506f0bb8 100644
--- a/gcrypt.c
+++ b/gcrypt.c
@@ -46,6 +46,22 @@ void hash_function(const char *data, unsigned long len, unsigned char *hash)
gcry_md_close(handle);
}
+void hash2_function(const char *data, unsigned long len, unsigned char *hash)
+{
+ gcry_error_t gret;
+ gcry_md_hd_t handle;
+ unsigned char *md;
+
+ gret = gcry_md_open(&handle, GCRY_MD_SHA256, 0);
+ assert(gret == 0);
+ gcry_md_write(handle, data, (size_t)len);
+ gcry_md_final(handle);
+ md = gcry_md_read(handle, GCRY_MD_SHA256);
+ assert(md);
+ memcpy(hash, md, HASH2_SIZE);
+ gcry_md_close(handle);
+}
+
void get_random_bytes_or_die(unsigned char *buf, int num)
{
gcry_randomize(buf, (size_t)num, GCRY_STRONG_RANDOM);
diff --git a/interactive.c b/interactive.c
index 9f4dec17..041376a1 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)
{
@@ -561,7 +561,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.suite.m4 b/m4/lls/server.suite.m4
index 93e4b573..1c941c43 100644
--- a/m4/lls/server.suite.m4
+++ b/m4/lls/server.suite.m4
@@ -133,7 +133,7 @@ version-string = GIT_VERSION()
typestr = directory
[help]
The directory which contains the database for the audio file
- selector. The default is ~/.paraslash/afs_database-0.4.
+ selector. The default is ~/.paraslash/afs_database-0.7.
If no database was found, the "init" command must be executed to
initialize the database. Once initialized, audio files may added with
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/m4/lls/upgrade_db.suite.m4 b/m4/lls/upgrade_db.suite.m4
new file mode 100644
index 00000000..7f46b749
--- /dev/null
+++ b/m4/lls/upgrade_db.suite.m4
@@ -0,0 +1,33 @@
+m4_define(PROGRAM, para_upgrade_db)
+[suite upgrade_db]
+version-string = GIT_VERSION()
+[supercommand para_upgrade_db]
+ purpose = upgrade the paraslash database to version 0.7
+ [description]
+ The database format changes with paraslash-0.7.0. This program converts
+ the database from the older 0.4 format that was used in paraslash 0.4.x
+ through 0.6.x. In has to be executed only once.
+ [/description]
+ m4_include(common-option-section.m4)
+ m4_include(help.m4)
+ m4_include(detailed-help.m4)
+ m4_include(version.m4)
+ m4_include(loglevel.m4)
+ [option src-database-dir]
+ summary = location of the old afs database
+ arg_info = required_arg
+ arg_type = string
+ typestr = directory
+ [help]
+ The directory which contains the database to be converted. The default
+ is ~/.paraslash/afs_database-0.4.
+ [/help]
+ [option dst-database-dir]
+ summary = location of the new afs database
+ arg_info = required_arg
+ arg_type = string
+ typestr = directory
+ [help]
+ The directory which contains the converted database after the program
+ has terminated. The default is ~/.paraslash/afs_database-0.7.
+ [/help]
diff --git a/mm.c b/mm.c
deleted file mode 100644
index 358783a1..00000000
--- a/mm.c
+++ /dev/null
@@ -1,375 +0,0 @@
-/* Copyright (C) 2007 Andre Noll , see file COPYING. */
-
-/** \file mm.c Paraslash's mood methods. */
-
-#include
-#include
-#include
-#include
-
-#include "para.h"
-#include "error.h"
-#include "string.h"
-#include "afh.h"
-#include "afs.h"
-#include "mm.h"
-
-/** The comparators for numeric mood methods (year, bitrate, ...). */
-#define MOOD_COMPARATORS \
- MC(LESS, <) \
- MC(LESS_OR_EQUAL, <=) \
- MC(EQUAL, =) \
- MC(EQUAL2, ==) \
- MC(NOT_EQUAL, !=) \
- MC(NOT_EQUAL2, <>) \
- MC(GREATER, >) \
- MC(GREATER_OR_EQUAL, >=) \
-
-/** Prefix mood comparator name with "_MC", example: MC_LESS. */
-#define MC(a, b) MC_ ## a,
-/** Each mood comparator is identified by an integer of this type. */
-enum mood_comparator_id {MOOD_COMPARATORS NUM_MOOD_COMPARATORS};
-#undef MC
-/** Stringfied mood comparator, example: "<". */
-#define MC(a, b) # b,
-/** Array of mood comparators represented as C strings ("<", "<=", ...). */
-static const char *mood_comparators[] = {MOOD_COMPARATORS};
-#undef MC
-
-static int parse_mood_comparator(const char *word)
-{
- int i;
-
- for (i = 0; i < NUM_MOOD_COMPARATORS; i++)
- if (!strcmp(word, mood_comparators[i]))
- return i;
- return -E_MOOD_SYNTAX;
-}
-
-struct mm_compare_num_data {
- /** <, <=, =, !=, >=, or >. */
- enum mood_comparator_id id;
- /** The value given at the mood line. */
- int32_t arg;
-};
-
-static int mm_compare_num_score_function(int32_t val,
- const struct mm_compare_num_data *cnd)
-{
- int res;
- int32_t arg = cnd->arg;
-
- switch (cnd->id) {
- case MC_LESS:
- res = val < arg; break;
- case MC_LESS_OR_EQUAL:
- res = val <= arg; break;
- case MC_EQUAL:
- case MC_EQUAL2:
- res = val == arg; break;
- case MC_NOT_EQUAL:
- case MC_NOT_EQUAL2:
- res = val != arg; break;
- case MC_GREATER:
- res = val > arg; break;
- case MC_GREATER_OR_EQUAL:
- res = val >= arg; break;
- default:
- PARA_EMERG_LOG("BUG: invalid mood comparator\n");
- exit(EXIT_FAILURE);
- }
- return res? 100 : -100;
-}
-
-static int mm_compare_num_parser(int argc, char **argv, void **private)
-{
- int ret;
- enum mood_comparator_id id;
- int32_t arg;
- struct mm_compare_num_data *cnd;
- if (argc != 2)
- return -E_MOOD_SYNTAX;
- ret = parse_mood_comparator(argv[1]);
- if (ret < 0)
- return ret;
- id = ret;
- ret = para_atoi32(argv[2], &arg);
- if (ret < 0)
- return ret;
- cnd = para_malloc(sizeof(struct mm_compare_num_data));
- cnd->id = id;
- cnd->arg = arg;
- *private = cnd;
- return 1;
-}
-
-static int mm_regex_parser(int argc, char **argv, void **private)
-{
- regex_t *preg;
- int ret;
-
- if (argc != 1)
- return -E_MOOD_SYNTAX;
- preg = para_malloc(sizeof(*preg));
- ret = para_regcomp(preg, argv[1], REG_EXTENDED | REG_NOSUB);
- if (ret < 0) {
- free(preg);
- return ret;
- }
- *private = preg;
- return 1;
-}
-
-static int mm_regex_score_function(const regex_t *preg, const char *pattern)
-{
- return regexec(preg, pattern, 0, NULL, 0) == 0? 100 : -100;
-}
-
-static void mm_regex_cleanup(void *private)
-{
- regex_t *preg = private;
- regfree(preg);
- free(preg);
-}
-
-static int mm_artist_matches_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_regex_score_function(private, afhi->tags.artist);
-}
-
-static int mm_title_matches_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_regex_score_function(private, afhi->tags.title);
-}
-
-static int mm_album_matches_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_regex_score_function(private, afhi->tags.album);
-}
-
-static int mm_comment_matches_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_regex_score_function(private, afhi->tags.comment);
-}
-
-static int mm_bitrate_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afhi->bitrate, private);
-}
-
-static int mm_frequency_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afhi->frequency, private);
-}
-
-static int mm_channels_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afhi->channels, private);
-}
-
-static int mm_image_id_score_function(__a_unused const char *path,
- const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afsi->image_id, private);
-}
-
-static int mm_lyrics_id_score_function(__a_unused const char *path,
- const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afsi->lyrics_id, private);
-}
-
-static int mm_num_played_score_function(__a_unused const char *path,
- const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afsi->num_played, private);
-}
-
-struct mm_year_data {
- /** Comparator and year given at the mood line. */
- struct mm_compare_num_data *cnd;
- /** Used to detect Y2K issues. */
- int32_t current_year;
-};
-
-static int mm_year_parser(int argc, char **argv, void **private)
-{
- int ret;
- struct mm_year_data *mmyd = para_malloc(sizeof(*mmyd));
- time_t current_time;
- struct tm *gmt;
-
- ret = mm_compare_num_parser(argc, argv, (void **)&mmyd->cnd);
- if (ret < 0)
- goto err;
- current_time = time(NULL);
- gmt = gmtime(¤t_time);
- /* tm_year is the number of years since 1900 */
- mmyd->current_year = gmt->tm_year + 1900;
- *private = mmyd;
- return 1;
-err:
- free(mmyd);
- return ret;
-}
-
-static int mm_year_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- const struct mm_year_data *mmyd = private;
- int32_t tag_year;
- int ret = para_atoi32(afhi->tags.year, &tag_year);
-
- if (ret < 0) /* year tag not present or not a number */
- return -100;
- if (tag_year < 0)
- return -100;
- /* try to work around Y2K issues */
- if (tag_year < 100) {
- tag_year += 1900;
- if (tag_year + 100 <= mmyd->current_year)
- tag_year += 100; /* assume tag_year >= 2000 */
- }
- return mm_compare_num_score_function(tag_year, mmyd->cnd);
-}
-
-static void mm_year_cleanup(void *private)
-{
- struct mm_year_data *mmyd = private;
-
- free(mmyd->cnd);
- free(mmyd);
-}
-
-static int mm_no_attributes_set_parser(int argc, __a_unused char **argv,
- __a_unused void **ignored)
-{
- return argc? -E_MOOD_SYNTAX : 1;
-}
-
-static int mm_no_attributes_set_score_function(__a_unused const char *path,
- const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- __a_unused const void *data)
-{
- if (!afsi->attributes)
- return 100;
- return -100;
-}
-
-static int mm_path_matches_score_function(const char *path,
- __a_unused const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *data)
-{
- if (fnmatch(data, path, 0))
- return -100;
- return 100;
-}
-
-static int mm_path_matches_parser(int argc, char **argv, void **data)
-{
- if (argc != 1)
- return -E_MOOD_SYNTAX;
- *data = para_strdup(argv[1]);
- return 1;
-}
-
-static void mm_path_matches_cleanup(void *data)
-{
- free(data);
-}
-
-static int mm_is_set_parser(int argc, char **argv, void **bitnum)
-{
- int ret;
- unsigned char c, *res;
-
- if (argc != 1)
- return -E_MOOD_SYNTAX;
- ret = get_attribute_bitnum_by_name(argv[1], &c);
- if (ret < 0)
- return ret;
- res = para_malloc(1);
- *res = c;
- *bitnum = res;
- return 1;
-}
-
-static int mm_is_set_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *data)
-{
- const unsigned char *bn = data;
- if (afsi->attributes & (1ULL << *bn))
- return 100;
- return -100;
-}
-
-#define DEFINE_MOOD_METHOD(_name) \
-.parser = mm_ ## _name ## _parser, \
-.score_function = mm_ ## _name ## _score_function, \
-.name = #_name
-
-#define DEFINE_MOOD_METHOD_WITH_CLEANUP(_name) \
- DEFINE_MOOD_METHOD(_name), \
- .cleanup = mm_ ## _name ## _cleanup
-
-#define DEFINE_REGEX_MOOD_METHOD(_name) \
- .name = #_name, \
- .parser = mm_regex_parser, \
- .score_function = mm_ ## _name ## _score_function, \
- .cleanup = mm_regex_cleanup
-
-#define DEFINE_COMPARE_NUM_MOOD_METHOD(_name) \
- .name = #_name, \
- .parser = mm_compare_num_parser, \
- .score_function = mm_ ## _name ## _score_function
-
-const struct mood_method mood_methods[] = {
- {DEFINE_MOOD_METHOD(no_attributes_set)},
- {DEFINE_MOOD_METHOD(is_set)},
- {DEFINE_MOOD_METHOD_WITH_CLEANUP(path_matches)},
- {DEFINE_MOOD_METHOD_WITH_CLEANUP(year)},
- {DEFINE_REGEX_MOOD_METHOD(artist_matches)},
- {DEFINE_REGEX_MOOD_METHOD(title_matches)},
- {DEFINE_REGEX_MOOD_METHOD(album_matches)},
- {DEFINE_REGEX_MOOD_METHOD(comment_matches)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(bitrate)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(frequency)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(channels)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(num_played)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(image_id)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(lyrics_id)},
- {.parser = NULL}
-};
diff --git a/mm.h b/mm.h
deleted file mode 100644
index 28038e72..00000000
--- a/mm.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Copyright (C) 2007 Andre Noll , see file COPYING. */
-
-/** \file mm.h Symbols and declarations for mood methods. */
-
-/**
- * Assign scores according to a mood_method.
- *
- * Each mood_method has its own mood_score_function. The first three parameters
- * passed to that function are informations about the audio file whose score is
- * to be computed. The data argument depends on the mood method this function
- * is used for. It usually is the argument given at the end of a mood line.
- *
- * Mood score functions must return values between -100 and +100 inclusively.
- * Boolean score functions should always return either -100 or +100.
- *
- * \sa struct \ref mood_method, \ref mood_parser.
- */
-typedef int mood_score_function(const char *path, const struct afs_info *afsi,
- const struct afh_info *afhi, const void *data);
-
-/**
- * Pre-process a mood line.
- *
- * The mood_parser of a mood_method is called once at mood open time for each
- * line of the current mood definition that contains the mood_method's name as
- * a keyword. The line is passed to the mood_parser as the first argument. The
- * mood_parser must determine whether the line is syntactically correct and
- * return a positive value if so and a negative value otherwise.
- *
- * Some mood parsers pre-process the data given in the mood line to compute a
- * structure which depends of the particular mood_method and which is used
- * later in the mood_score_function of the mood_method. The mood_parser may
- * store a pointer to its structure via the void** pointer.
- *
- * \sa \ref mood_cleanup_function, \ref mood_score_function.
- */
-typedef int mood_parser(int, char **, void **);
-
-/**
- * Deallocate resources which were allocated by the mood_parser.
- *
- * Function to free the resources allocated in \ref mood_method::parser. The
- * argument is a pointer to mood method specific data returned by ->parser().
- */
-typedef void mood_cleanup_function(void *);
-
-/**
- * Used for scoring and to determine whether a file is admissible.
- */
-struct mood_method {
- /** The name of the method. */
- const char *name;
- /** Pointer to the mood parser. */
- mood_parser *parser;
- /** Pointer to the score function */
- mood_score_function *score_function;
- /** Optional cleanup function. */
- mood_cleanup_function *cleanup;
-};
-
-/** The array of available mood methods. */
-extern const struct mood_method mood_methods[];
diff --git a/mood.c b/mood.c
index 5268e77f..b3f007b6 100644
--- a/mood.c
+++ b/mood.c
@@ -12,7 +12,6 @@
#include "afh.h"
#include "afs.h"
#include "list.h"
-#include "mm.h"
#include "mood.h"
/*
@@ -39,47 +38,21 @@ 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.
- */
-struct mood_item {
- /** The method this line is referring to. */
- const struct mood_method *method;
- /** The data structure computed by the mood parser. */
- void *parser_data;
- /** The given score value, or zero if none was given. */
- int32_t score_arg;
- /** Non-zero if random scoring was requested. */
- int random_score;
- /** Whether the "not" keyword was given in the mood line. */
- int logical_not;
- /** The position in the list of items. */
- struct list_head mood_item_node;
-};
-
-/*
- * Created from the mood definition by \ref change_current_mood().
- *
- * When a mood is opened, each line of its definition is investigated, and a
- * corresponding mood item is produced. Each mood line starts with accept,
- * deny, or score which determines the type of the mood line. For each such
- * type a linked list is maintained whose entries are the mood items.
- */
struct mood {
/** The name of this mood. */
char *name;
- /** The list of mood items of type \p accept. */
- struct list_head accept_list;
- /** The list of mood items of type \p deny. */
- struct list_head deny_list;
- /** The list of mood items of type \p score. */
- struct list_head score_list;
- /* Only used for version 2 moods. */
+ /** Info for the bison parser. */
struct mp_context *parser_context;
};
@@ -146,115 +119,20 @@ __a_const static uint64_t int_sqrt(uint64_t x)
return res;
}
-/*
- * Returns true if row matches, false if it does not match. In any case score
- * and score_arg_sum are set/increased accordingly.
- */
-static bool get_item_score(struct mood_item *item, const struct afs_info *afsi,
- const struct afh_info *afhi, const char *path, long *score,
- long *score_arg_sum)
-{
- int ret;
- bool match = true;
-
- *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg);
- ret = 100;
- if (item->method) {
- ret = item->method->score_function(path, afsi, afhi,
- item->parser_data);
- if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not))
- match = false;
- }
- if (item->random_score)
- *score = PARA_ABS(ret) * para_random(100);
- else
- *score = PARA_ABS(ret) * item->score_arg;
- return match;
-}
-
/* returns 1 if row admissible, 0 if not, negative on errors */
-static int row_is_admissible(const struct osl_row *aft_row, struct mood *m,
- long *scorep)
+static int row_is_admissible(const struct osl_row *aft_row, struct mood *m)
{
- struct mood_item *item;
- int ret;
- bool match;
- long score_arg_sum = 0, score = 0, item_score;
- struct afs_info afsi;
- struct afh_info afhi;
- char *path;
-
if (!m)
return -E_NO_MOOD;
- if (m->parser_context) {
- *scorep = 0;
- return mp_eval_row(aft_row, m->parser_context);
- }
- ret = get_afsi_of_row(aft_row, &afsi);
- if (ret < 0)
- return ret;
- ret = get_afhi_of_row(aft_row, &afhi);
- if (ret < 0)
- return ret;
- ret = get_audio_file_path_of_row(aft_row, &path);
- if (ret < 0)
- return ret;
- /* reject audio file if it matches any entry in the deny list */
- list_for_each_entry(item, &m->deny_list, mood_item_node) {
- match = get_item_score(item, &afsi, &afhi, path, &item_score,
- &score_arg_sum);
- if (match) /* not admissible */
- return 0;
- score += item_score;
- }
- match = false;
- list_for_each_entry(item, &m->accept_list, mood_item_node) {
- ret = get_item_score(item, &afsi, &afhi, path, &item_score,
- &score_arg_sum);
- if (ret == 0)
- continue;
- match = true;
- score += item_score;
- }
- /* reject if there is no matching entry in the accept list */
- if (!match && !list_empty(&m->accept_list))
- return 0;
- list_for_each_entry(item, &m->score_list, mood_item_node) {
- match = get_item_score(item, &afsi, &afhi, path, &item_score,
- &score_arg_sum);
- if (match)
- score += item_score;
- }
- if (score_arg_sum)
- score /= score_arg_sum;
- *scorep = score;
- return 1;
-}
-
-static void cleanup_list_entry(struct mood_item *item)
-{
- if (item->method && item->method->cleanup)
- item->method->cleanup(item->parser_data);
- else
- free(item->parser_data);
- list_del(&item->mood_item_node);
- free(item);
+ return mp_eval_row(aft_row, m->parser_context);
}
static void destroy_mood(struct mood *m)
{
- struct mood_item *tmp, *item;
-
if (!m)
return;
- list_for_each_entry_safe(item, tmp, &m->accept_list, mood_item_node)
- cleanup_list_entry(item);
- list_for_each_entry_safe(item, tmp, &m->deny_list, mood_item_node)
- cleanup_list_entry(item);
- list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node)
- cleanup_list_entry(item);
- free(m->name);
mp_shutdown(m->parser_context);
+ free(m->name);
free(m);
}
@@ -263,160 +141,16 @@ 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);
return m;
}
-/** The different types of a mood line. */
-enum mood_line_type {
- /** Invalid. */
- ML_INVALID,
- /** Accept line. */
- ML_ACCEPT,
- /** Deny line. */
- ML_DENY,
- /** Score line. */
- ML_SCORE
-};
-
-/** Data passed to the parser of a mood line. */
-struct mood_line_parser_data {
- /** The mood this mood line belongs to. */
- struct mood *m;
- /** The line number in the mood definition. */
- unsigned line_num;
-};
-
-/*
- * ] | deny [with score ] | score >
- * [if] [not] [options]
- * is either an integer or "random" which assigns a random score to
- * all matching files
- */
-static int parse_mood_line(char *mood_line, void *data)
-{
- struct mood_line_parser_data *mlpd = data;
- char **argv;
- unsigned num_words;
- char **w;
- int i, ret;
- enum mood_line_type mlt = ML_INVALID;
- struct mood_item *mi = NULL;
-
- mlpd->line_num++;
- ret = create_argv(mood_line, " \t", &argv);
- if (ret < 0)
- return ret;
- num_words = ret;
- if (!num_words) /* empty line */
- goto out;
- w = argv;
- if (**w == '#') /* comment */
- goto out;
- if (!strcmp(*w, "accept"))
- mlt = ML_ACCEPT;
- else if (!strcmp(*w, "deny"))
- mlt = ML_DENY;
- else if (!strcmp(*w, "score"))
- mlt = ML_SCORE;
- ret = -E_MOOD_SYNTAX;
- if (mlt == ML_INVALID)
- goto out;
- mi = para_calloc(sizeof(struct mood_item));
- if (mlt != ML_SCORE) {
- ret = -E_MOOD_SYNTAX;
- w++;
- if (!*w)
- goto out;
- if (strcmp(*w, "with"))
- goto check_for_if;
- w++;
- if (!*w)
- goto out;
- if (strcmp(*w, "score"))
- goto out;
- }
- if (mlt == ML_SCORE || !strcmp(*w, "score")) {
- ret = -E_MOOD_SYNTAX;
- w++;
- if (!*w)
- goto out;
- if (strcmp(*w, "random")) {
- mi->random_score = 0;
- ret = para_atoi32(*w, &mi->score_arg);
- if (ret < 0)
- goto out;
- } else {
- mi->random_score = 1;
- if (!*(w + 1))
- goto success; /* the line "score random" is valid */
- }
- } else
- mi->score_arg = 0;
- ret = -E_MOOD_SYNTAX;
- w++;
- if (!*w)
- goto out;
-check_for_if:
- if (!strcmp(*w, "if")) {
- ret = -E_MOOD_SYNTAX;
- w++;
- if (!*w)
- goto out;
- }
- if (!strcmp(*w, "not")) {
- ret = -E_MOOD_SYNTAX;
- w++;
- if (!*w)
- goto out;
- mi->logical_not = 1;
- } else
- mi->logical_not = 0;
- for (i = 0; mood_methods[i].parser; i++) {
- if (strcmp(*w, mood_methods[i].name))
- continue;
- break;
- }
- ret = -E_MOOD_SYNTAX;
- if (!mood_methods[i].parser)
- goto out;
- ret = mood_methods[i].parser(num_words - 1 - (w - argv), w,
- &mi->parser_data);
- if (ret < 0)
- goto out;
- mi->method = &mood_methods[i];
-success:
- if (mlpd->m) {
- if (mlt == ML_ACCEPT)
- para_list_add(&mi->mood_item_node, &mlpd->m->accept_list);
- else if (mlt == ML_DENY)
- para_list_add(&mi->mood_item_node, &mlpd->m->deny_list);
- else
- para_list_add(&mi->mood_item_node, &mlpd->m->score_list);
- }
- PARA_DEBUG_LOG("%s entry added, method: %p\n", mlt == ML_ACCEPT? "accept" :
- (mlt == ML_DENY? "deny" : "score"), mi->method);
- ret = 1;
-out:
- free_argv(argv);
- if (mi && (ret < 0 || !mlpd->m)) { /* mi was not added to any list */
- free(mi->parser_data);
- free(mi);
- }
- return ret;
-}
-
static int load_mood(const struct osl_row *mood_row, struct mood **m,
char **errmsg)
{
char *mood_name;
struct osl_object mood_def;
- struct mood_line_parser_data mlpd = {.line_num = 0};
int ret;
- *m = NULL;
ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
if (ret < 0) {
if (errmsg)
@@ -425,33 +159,21 @@ static int load_mood(const struct osl_row *mood_row, struct mood **m,
return ret;
}
assert(*mood_name);
- mlpd.m = alloc_new_mood(mood_name);
- ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
- parse_mood_line, &mlpd);
- if (ret < 0) {
- PARA_INFO_LOG("opening version 2 mood %s\n", mlpd.m->name);
- ret = mp_init(mood_def.data, mood_def.size, &mlpd.m->parser_context,
- errmsg);
- if (ret < 0)
- destroy_mood(mlpd.m);
- } else {
- PARA_WARNING_LOG("loaded version 1 mood %s\n", mlpd.m->name);
- PARA_WARNING_LOG("please convert to version 2\n");
- ret = 1;
- }
+ *m = alloc_new_mood(mood_name);
+ PARA_INFO_LOG("opening mood %s\n", mood_name);
+ ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, errmsg);
osl_close_disk_object(&mood_def);
- if (ret >= 0)
- *m = mlpd.m;
+ if (ret < 0)
+ destroy_mood(*m);
return ret;
}
static int check_mood(struct osl_row *mood_row, void *data)
{
struct para_buffer *pb = data;
- char *mood_name;
+ char *mood_name, *errmsg;
struct osl_object mood_def;
- struct mood_line_parser_data mlpd = {.line_num = 0};
-
+ struct mood *m;
int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
if (ret < 0) {
@@ -460,24 +182,15 @@ static int check_mood(struct osl_row *mood_row, void *data)
}
if (!*mood_name) /* ignore dummy row */
goto out;
- ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
- parse_mood_line, &mlpd);
+ m = alloc_new_mood("check");
+ ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
+ &errmsg);
if (ret < 0) {
- char *errmsg;
- struct mood *m = alloc_new_mood("check");
- ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
- &errmsg);
- if (ret < 0) {
- para_printf(pb, "%s: %s\n", mood_name, errmsg);
- free(errmsg);
- para_printf(pb, "%s\n", para_strerror(-ret));
- } else
- destroy_mood(m);
- } else {
- para_printf(pb, "%s: v1 mood, please convert to v2\n",
- mood_name);
-
- }
+ para_printf(pb, "%s: %s\n", mood_name, errmsg);
+ free(errmsg);
+ para_printf(pb, "%s\n", para_strerror(-ret));
+ } else
+ destroy_mood(m);
ret = 1; /* don't fail the loop on invalid mood definitions */
out:
osl_close_disk_object(&mood_def);
@@ -499,6 +212,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)
@@ -506,13 +256,13 @@ static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd)
return 100 * (n * x - sum) / (int64_t)int_sqrt(n) / (int64_t)int_sqrt(qd);
}
-static long compute_score(struct afs_info *afsi, long mood_score)
+static long compute_score(struct afs_info *afsi)
{
- mood_score -= normalized_value(afsi->num_played, statistics.num,
+ long 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,
+ score -= normalized_value(afsi->last_played, statistics.num,
statistics.last_played_sum, statistics.last_played_qd);
- return mood_score / 3;
+ return score / 2;
}
static int add_afs_statistics(const struct osl_row *row)
@@ -556,6 +306,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;
}
@@ -581,63 +332,42 @@ static int del_afs_statistics(const struct osl_row *row)
/*
* At mood open time we determine the set of admissible files for the given
- * mood. The mood score of each admissible file is computed by adding up all
- * mood item scores. Next, we update the afs statistics and append a struct
- * admissible_file_info to a temporary array.
- *
- * When all files have been processed in this way, the final score of each
- * admissible file is computed by adding the dynamic score (which depends on
- * the afs_statistics and the current time) to the mood score. Finally, all
- * audio files in the temporary array are added to the score table and the
- * array is freed.
+ * mood where each file is identified by a pointer to a row of the audio file
+ * table. In the first pass the pointers are added to a temporary array and
+ * statistics are computed. When all admissible files have been processed in
+ * this way, the score of each admissible file is computed and the (row, score)
+ * pair is added to the score table. This has to be done in a second pass
+ * since the score depends on the statistics. Finally, the array is freed.
*/
-struct admissible_file_info
-{
- /** The admissible audio file. */
- struct osl_row *aft_row;
- /** Its score. */
- long score;
-};
-
-/** The temporary array of admissible files. */
struct admissible_array {
/** Files are admissible wrt. this mood. */
struct mood *m;
/** The size of the array */
unsigned size;
/** Pointer to the array of admissible files. */
- struct admissible_file_info *array;
+ struct osl_row **array;
};
-/**
- * Add an entry to the array of admissible files.
- *
- * \param aft_row The audio file to be added.
- * \param private_data Pointer to a struct admissible_file_info.
- *
- * \return 1 if row admissible, 0 if not, negative on errors.
+/*
+ * Check whether the given audio file is admissible. If it is, add it to array
+ * of admissible files.
*/
static int add_if_admissible(struct osl_row *aft_row, void *data)
{
struct admissible_array *aa = data;
int ret;
- long score = 0;
- ret = row_is_admissible(aft_row, aa->m, &score);
+ ret = row_is_admissible(aft_row, aa->m);
if (ret <= 0)
return ret;
if (statistics.num >= aa->size) {
aa->size *= 2;
aa->size += 100;
aa->array = para_realloc(aa->array,
- aa->size * sizeof(struct admissible_file_info));
+ aa->size * sizeof(struct osl_row *));
}
- aa->array[statistics.num].aft_row = aft_row;
- aa->array[statistics.num].score = score;
- ret = add_afs_statistics(aft_row);
- if (ret < 0)
- return ret;
- return 1;
+ aa->array[statistics.num] = aft_row;
+ return add_afs_statistics(aft_row);
}
/**
@@ -704,7 +434,7 @@ static int update_afs_statistics(struct afs_info *old_afsi,
return 1;
}
-static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
+static int add_to_score_table(const struct osl_row *aft_row)
{
long score;
struct afs_info afsi;
@@ -712,7 +442,7 @@ static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
if (ret < 0)
return ret;
- score = compute_score(&afsi, mood_score);
+ score = compute_score(&afsi);
return score_add(aft_row, score);
}
@@ -770,7 +500,7 @@ static int mood_update_audio_file(const struct osl_row *aft_row,
if (ret < 0)
return ret;
was_admissible = ret;
- ret = row_is_admissible(aft_row, current_mood, &score);
+ ret = row_is_admissible(aft_row, current_mood);
if (ret < 0)
return ret;
is_admissible = (ret > 0);
@@ -782,7 +512,7 @@ static int mood_update_audio_file(const struct osl_row *aft_row,
ret = add_afs_statistics(aft_row);
if (ret < 0)
return ret;
- return add_to_score_table(aft_row, score);
+ return add_to_score_table(aft_row);
}
/* update score */
ret = get_afsi_of_row(aft_row, &afsi);
@@ -793,7 +523,7 @@ static int mood_update_audio_file(const struct osl_row *aft_row,
if (ret < 0)
return ret;
}
- score = compute_score(&afsi, score);
+ score = compute_score(&afsi);
PARA_DEBUG_LOG("score: %li\n", score);
percent = (score + 100) / 3;
if (percent > 100)
@@ -804,15 +534,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 +548,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 +572,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;
}
/**
@@ -868,6 +618,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;
@@ -904,9 +659,11 @@ int change_current_mood(const char *mood_name, char **errmsg)
*errmsg = make_message("audio file loop failed");
goto out;
}
+ 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);
+ ret = add_to_score_table(aa.array[i]);
if (ret < 0) {
if (errmsg)
*errmsg = make_message(
@@ -914,7 +671,6 @@ int change_current_mood(const char *mood_name, char **errmsg)
goto out;
}
}
- log_statistics();
ret = statistics.num;
out:
free(aa.array);
@@ -959,9 +715,17 @@ static int reload_current_mood(void)
* \param pb Unused.
* \param data Its type depends on the event.
*
- * This function performs actions required due to the occurrence of the given
- * event. Possible actions include reload of the current mood and update of the
- * score of an audio file.
+ * This function updates the score table according to the event that has
+ * occurred. Two actions are possible: (a) reload the current mood, or (b)
+ * add/remove/update the row of the score table which corresponds to the audio
+ * file that has been modified or whose afs info has been changed. It depends
+ * on the type of the event which action (if any) is performed.
+ *
+ * The callbacks of command handlers such as com_add() or com_touch() which
+ * modify the audio file table call this function. The virtual streaming system
+ * also calls this after it has updated the afs info of the file it is about to
+ * stream (the one with the highest score). If the file stays admissible, its
+ * score is recomputed so that a different file is picked next time.
*
* \return Standard.
*/
diff --git a/mp.c b/mp.c
index aea767d9..c537e729 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.
*
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/openssl.c b/openssl.c
index 0ad9d7db..32891cbb 100644
--- a/openssl.c
+++ b/openssl.c
@@ -414,3 +414,11 @@ void hash_function(const char *data, unsigned long len, unsigned char *hash)
SHA1_Update(&c, data, len);
SHA1_Final(hash, &c);
}
+
+void hash2_function(const char *data, unsigned long len, unsigned char *hash)
+{
+ SHA256_CTX c;
+ SHA256_Init(&c);
+ SHA256_Update(&c, data, len);
+ SHA256_Final(hash, &c);
+}
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 ffdc8555..14fac42f 100644
--- a/play.c
+++ b/play.c
@@ -1102,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 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/score.c b/score.c
index 98358933..894e8ca3 100644
--- a/score.c
+++ b/score.c
@@ -21,15 +21,11 @@ static int ptr_compare(const struct osl_object *obj1, const struct osl_object *o
return NUM_COMPARE(d1, d2);
}
-/**
- * Compare the score of two audio files
- *
- * \param obj1 Pointer to the first score object.
- * \param obj2 Pointer to the second score object.
- *
- * This function first compares the score values as usual integers. If they compare as
- * equal, the address of \a obj1 and \a obj2 are compared. So this compare function
- * returns zero if and only if \a obj1 and \a obj2 point to the same memory area.
+/*
+ * This function first compares the score values. If they are equal, the
+ * addresses of the two objects are compared. Thus, the function returns
+ * "equal" only if the two objects alias each other, i.e., point to the same
+ * memory address.
*/
static int score_compare(const struct osl_object *obj1, const struct osl_object *obj2)
{
@@ -92,16 +88,7 @@ int get_num_admissible_files(unsigned *num)
return osl(osl_get_num_rows(score_table, num));
}
-/**
- * Get the score of the audio file associated with given row of the score table.
- *
- * \param score_row Pointer to the row in the score table.
- * \param score Result is returned here on success.
- *
- * On errors (negative return value) the content of \a score is undefined.
- *
- * \return The return value of the underlying call to osl_get_object().
- */
+/* On errors (negative return value) the content of score is undefined. */
static int get_score_of_row(void *score_row, long *score)
{
struct osl_object obj;
@@ -202,8 +189,7 @@ int score_update(const struct osl_row *aft_row, long percent)
* \param score Result pointer.
* \param aft_row Result pointer.
*
- * \return Negative on errors, positive on success. Possible errors: Errors
- * returned by osl_get_object().
+ * \return Standard.
*/
int get_score_and_aft_row(struct osl_row *score_row, long *score,
struct osl_row **aft_row)
@@ -250,8 +236,7 @@ int admissible_file_loop(void *data, osl_rbtree_loop_func *func)
* \param aft_row Points to the row in the aft of the "best" audio file.
* \param score Highest score value in the score table.
*
- * \return Positive on success, negative on errors. Possible errors: Errors
- * returned by osl_rbtree_last_row(), osl_get_object().
+ * \return Standard.
*/
int score_get_best(struct osl_row **aft_row, long *score)
{
@@ -273,8 +258,7 @@ int score_get_best(struct osl_row **aft_row, long *score)
*
* \param aft_row The file which is no longer admissible.
*
- * \return Positive on success, negative on errors. Possible errors:
- * Errors returned by osl_get_row() and osl_del_row().
+ * \return Standard.
*
* \sa \ref score_add().
*/
@@ -315,20 +299,12 @@ int row_belongs_to_score_table(const struct osl_row *aft_row, unsigned *rank)
return 1;
}
-/* Close the score table. */
static void score_close(void)
{
osl_close_table(score_table, OSL_FREE_VOLATILE);
score_table = NULL;
}
-/**
- * Open the score table.
- *
- * \param dir Unused.
- *
- * \return The return value of the underlying call to osl_open_table().
- */
static int score_open(__a_unused const char *dir)
{
score_table_desc.dir = NULL; /* this table has only volatile columns */
diff --git a/send_common.c b/send_common.c
index ea494d9a..90242d5c 100644
--- a/send_common.c
+++ b/send_common.c
@@ -44,12 +44,12 @@
*/
void shutdown_client(struct sender_client *sc, struct sender_status *ss)
{
- PARA_INFO_LOG("shutting down %s on fd %d\n", sc->name, sc->fd);
- free(sc->name);
if (!process_is_command_handler()) {
+ PARA_INFO_LOG("shutting down %s on fd %d\n", sc->name, sc->fd);
close(sc->fd);
del_close_on_fork_list(sc->fd);
}
+ free(sc->name);
cq_destroy(sc->cq);
list_del(&sc->node);
free(sc->private_data);
@@ -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 d51e4e0b..e0df714b 100644
--- a/server.c
+++ b/server.c
@@ -2,31 +2,6 @@
/** \file server.c Paraslash's main server. */
-/**
- * \mainpage Main data structures and selected APIs:
- *
- * - Senders: \ref sender,
- * - Audio file selector: \ref afs_info, \ref afs_table,
- * - Audio format handler: \ref audio_format_handler, \ref afh_info
- * - Receivers/filters/writers: \ref receiver, \ref receiver_node,
- * \ref filter, \ref filter_node, \ref writer_node, \ref writer.
- * - Scheduling: \ref sched.h,
- * - Buffer trees: \ref buffer_tree.h,
- * - Sideband API: \ref sideband.h,
- * - Crypto: \ref crypt.h, \ref crypt_backend.h,
- * - Error subsystem: \ref error.h,
- * - Inter process communication: \ref ipc.h,
- * - Forward error correction: \ref fec.h,
- * - Daemons: \ref daemon.h,
- * - Mixer API: \ref mix.h,
- * - Interactive sessions: \ref interactive.h,
- * - File descriptors: \ref fd.h,
- * - Signals: \ref signal.h,
- * - Networking: \ref net.h,
- * - Time: \ref time.c,
- * - Doubly linked lists: \ref list.h.
- */
-
#include
#include
#include
@@ -704,12 +679,13 @@ int main(int argc, char *argv[])
deplete_close_on_fork_list();
if (ret < 0)
PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+ vss_shutdown();
} else {
+ vss_shutdown();
alarm(ALARM_TIMEOUT);
close_listed_fds();
ret = handle_connect(sct->child_fd);
}
- vss_shutdown();
shm_detach(mmd);
user_list_deplete();
free_lpr();
diff --git a/string.c b/string.c
index 198e9f1d..f8033190 100644
--- a/string.c
+++ b/string.c
@@ -245,56 +245,6 @@ __must_check __malloc char *para_strcat(char *a, const char *b)
return tmp;
}
-/**
- * Paraslash's version of dirname().
- *
- * \param name Pointer to the full path.
- *
- * Compute the directory component of \p name.
- *
- * \return If \a name is \p NULL or the empty string, return \p NULL.
- * Otherwise, Make a copy of \a name and return its directory component. Caller
- * is responsible to free the result.
- */
-__must_check __malloc char *para_dirname(const char *name)
-{
- char *p, *ret;
-
- if (!name || !*name)
- return NULL;
- ret = para_strdup(name);
- p = strrchr(ret, '/');
- if (!p)
- *ret = '\0';
- else
- *p = '\0';
- return ret;
-}
-
-/**
- * Paraslash's version of basename().
- *
- * \param name Pointer to the full path.
- *
- * Compute the filename component of \a name.
- *
- * \return \p NULL if (a) \a name is the empty string or \p NULL, or (b) name
- * ends with a slash. Otherwise, a pointer within \a name is returned. Caller
- * must not free the result.
- */
-__must_check char *para_basename(const char *name)
-{
- char *ret;
-
- if (!name || !*name)
- return NULL;
- ret = strrchr(name, '/');
- if (!ret)
- return (char *)name;
- ret++;
- return ret;
-}
-
/**
* Get the logname of the current user.
*
diff --git a/string.h b/string.h
index 10379a0e..08b9965f 100644
--- a/string.h
+++ b/string.h
@@ -76,8 +76,6 @@ __printf_2_0 unsigned xvasprintf(char **result, const char *fmt, va_list ap);
__printf_2_3 unsigned xasprintf(char **result, const char *fmt, ...);
__must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...);
__must_check __malloc char *para_strcat(char *a, const char *b);
-__must_check __malloc char *para_dirname(const char *name);
-__must_check char *para_basename(const char *name);
__must_check __malloc char *para_logname(void);
__must_check __malloc char *para_homedir(void);
__malloc char *para_hostname(void);
diff --git a/sync_filter.c b/sync_filter.c
index f23545ad..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(
diff --git a/t/t0004-server.sh b/t/t0004-server.sh
index 03957464..9e681107 100755
--- a/t/t0004-server.sh
+++ b/t/t0004-server.sh
@@ -24,7 +24,8 @@ get_audio_file_paths ogg
declare -a oggs=($result)
declare -a oggs_base=(${oggs[@]##*/})
-declare -a commands=() cmdline=() required_objects=() good=() bad=()
+declare -a commands=() cmdline=() required_objects=() good=() bad=() \
+ expect_failure=()
i=0
commands[$i]="help"
cmdline[$i]="help -l"
@@ -72,6 +73,15 @@ required_objects[$i]='ogg_afh'
cmdline[$i]="ls -l=v ${oggs[@]}"
good[$i]='^attributes_txt: 33'
+let i++
+commands[$i]='addmood'
+cmdline[$i]="addmood test-mood"
+
+let i++
+commands[$i]='empty-mood-parameter'
+cmdline[$i]="select m/"
+expect_failure[$i]='true'
+
let i++
commands[$i]="term"
cmdline[$i]="term"
@@ -137,14 +147,19 @@ for ((i=0; i < ${#commands[@]}; i++)); do
continue
fi
fi
- test_expect_success "$command" "
+ if [[ -n "${expect_failure[$i]}" ]]; then
+ f=test_expect_failure
+ else
+ f=test_expect_success
+ fi
+ $f "$command" "
$PARA_CLIENT \
--loglevel $loglevel \
--server-port $port \
--key-file $privkey \
--config-file /dev/null \
-- \
- ${cmdline[$i]} > $command.out &&
+ ${cmdline[$i]} > $command.out < /dev/null &&
{ [[ -z \"${good[$i]}\" ]] || grep \"${good[$i]}\"; } < $command.out &&
{ [[ -z \"${bad[$i]}\" ]] || ! grep \"${bad[$i]}\"; } < $command.out
"
diff --git a/udp_send.c b/udp_send.c
index 96a25d37..68d75e3c 100644
--- a/udp_send.c
+++ b/udp_send.c
@@ -60,7 +60,7 @@ static void udp_close_target(struct sender_client *sc)
return;
if (ut->sent_fec_eof)
return;
- PARA_NOTICE_LOG("sending FEC EOF\n");
+ PARA_INFO_LOG("sending FEC EOF\n");
len = vss_get_fec_eof_packet(&buf);
/* Ignore write() errors since we are closing the target anyway. */
if (write(sc->fd, buf, len))
@@ -72,7 +72,8 @@ static void udp_delete_target(struct sender_client *sc, const char *msg)
{
struct udp_target *ut = sc->private_data;
- PARA_NOTICE_LOG("deleting %s (%s) from list\n", sc->name, msg);
+ if (!process_is_command_handler())
+ PARA_NOTICE_LOG("deleting %s (%s) from list\n", sc->name, msg);
udp_close_target(sc);
/* command handlers already called close_listed_fds() */
if (!process_is_command_handler()) {
@@ -248,7 +249,7 @@ static int udp_init_fec(struct sender_client *sc)
struct udp_target *ut = sc->private_data;
int mps;
- PARA_NOTICE_LOG("sending to udp %s\n", sc->name);
+ PARA_INFO_LOG("sending to udp %s\n", sc->name);
ut->sent_fec_eof = false;
mps = generic_max_transport_msg_size(sc->fd) - sizeof(struct udphdr);
PARA_INFO_LOG("current MPS = %d bytes\n", mps);
@@ -394,7 +395,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)
@@ -427,7 +428,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/upgrade_db.c b/upgrade_db.c
new file mode 100644
index 00000000..487d46c0
--- /dev/null
+++ b/upgrade_db.c
@@ -0,0 +1,386 @@
+/* Copyright (C) 2020 Andre Noll , see file COPYING. */
+
+/** \file upgrade_db.c Prepare the paraslash database for paraslash-0.7. */
+
+#include
+#include
+#include
+
+#include "upgrade_db.lsg.h"
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "fd.h"
+#include "crypt.h"
+#include "version.h"
+
+#define CMD_PTR (lls_cmd(0, upgrade_db_suite))
+#define OPT_RESULT(_name, _lpr) \
+ (lls_opt_result(LSG_UPGRADE_DB_PARA_UPGRADE_DB_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name, _lpr) (lls_opt_given(OPT_RESULT(_name, _lpr)))
+#define OPT_UINT32_VAL(_name, _lpr) (lls_uint32_val(0, OPT_RESULT(_name, _lpr)))
+#define OPT_STRING_VAL(_name, _lpr) (lls_string_val(0, OPT_RESULT(_name, _lpr)))
+
+static int loglevel;
+INIT_STDERR_LOGGING(loglevel);
+
+/** Array of error strings. */
+DEFINE_PARA_ERRLIST;
+
+static void handle_help_flag(struct lls_parse_result *lpr)
+{
+ char *help;
+
+ if (OPT_GIVEN(DETAILED_HELP, lpr))
+ help = lls_long_help(CMD_PTR);
+ else if (OPT_GIVEN(HELP, lpr))
+ help = lls_short_help(CMD_PTR);
+ else
+ return;
+ printf("%s\n", help);
+ free(help);
+ exit(EXIT_SUCCESS);
+}
+
+static struct stat *path_exists(const char *path)
+{
+ static struct stat sb;
+
+ if (stat(path, &sb) < 0)
+ return NULL;
+ return &sb;
+}
+
+static bool is_dir(const char *path)
+{
+ struct stat *sb = path_exists(path);
+ if (!sb)
+ return false;
+ return (sb->st_mode & S_IFMT) == S_IFDIR;
+}
+
+__noreturn static void die(const char *msg)
+{
+ PARA_EMERG_LOG("%s\n", msg);
+ exit(EXIT_FAILURE);
+}
+
+static int string_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+ const char *str1 = obj1->data;
+ const char *str2 = obj2->data;
+ return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size));
+}
+
+static char *src_db_dir, *dst_db_dir, *src_aft_dir, *dst_aft_dir;
+
+static void set_paths(const struct lls_parse_result *lpr)
+{
+ char *home = para_homedir();
+
+ if (OPT_GIVEN(SRC_DATABASE_DIR, lpr))
+ src_db_dir = para_strdup(OPT_STRING_VAL(SRC_DATABASE_DIR,
+ lpr));
+ else
+ src_db_dir = make_message(
+ "%s/.paraslash/afs_database-0.4", home);
+ if (OPT_GIVEN(DST_DATABASE_DIR, lpr))
+ dst_db_dir = para_strdup(OPT_STRING_VAL(DST_DATABASE_DIR,
+ lpr));
+ else
+ dst_db_dir = make_message(
+ "%s/.paraslash/afs_database-0.7", home);
+ free(home);
+ src_aft_dir = make_message("%s/audio_files", src_db_dir);
+ dst_aft_dir = make_message("%s/audio-files", src_db_dir);
+ PARA_NOTICE_LOG("source aft dir: %s\n", src_aft_dir);
+ PARA_NOTICE_LOG("destination aft dir: %s\n", dst_aft_dir);
+}
+
+static void check_sanity(void)
+{
+ PARA_INFO_LOG("checking source and destination directories\n");
+ if (!is_dir(src_db_dir))
+ die("source db directory does not exist");
+ if (path_exists(dst_db_dir))
+ die("destination db already exists");
+ if (!is_dir(src_aft_dir))
+ die("source audio file table does not exist");
+ if (path_exists(dst_aft_dir))
+ die("destination audio file table already exists");
+}
+
+/** The columns of the audio file table (both old and new). */
+enum audio_file_table_columns {
+ /** The hash on the content of the audio file. */
+ AFTCOL_HASH,
+ /** The full path in the filesystem. */
+ AFTCOL_PATH,
+ /** The audio file selector info. */
+ AFTCOL_AFSI,
+ /** The audio format handler info. */
+ AFTCOL_AFHI,
+ /** The chunk table info and the chunk table of the audio file. */
+ AFTCOL_CHUNKS,
+ /** The number of columns of this table. */
+ NUM_AFT_COLUMNS
+};
+
+#define AFSI_SIZE 32
+
+static int src_aft_hash_compare(const struct osl_object *obj1,
+ const struct osl_object *obj2)
+{
+ return hash_compare((unsigned char *)obj1->data,
+ (unsigned char *)obj2->data);
+}
+
+static struct osl_column_description src_aft_cols[] = {
+ [AFTCOL_HASH] = {
+ .storage_type = OSL_MAPPED_STORAGE,
+ .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
+ .name = "hash",
+ .compare_function = src_aft_hash_compare,
+ .data_size = HASH_SIZE
+ },
+ [AFTCOL_PATH] = {
+ .storage_type = OSL_MAPPED_STORAGE,
+ .storage_flags = OSL_RBTREE | OSL_UNIQUE,
+ .name = "path",
+ .compare_function = string_compare,
+ },
+ [AFTCOL_AFSI] = {
+ .storage_type = OSL_MAPPED_STORAGE,
+ .storage_flags = OSL_FIXED_SIZE,
+ .name = "afs_info",
+ .data_size = AFSI_SIZE
+ },
+ [AFTCOL_AFHI] = {
+ .storage_type = OSL_MAPPED_STORAGE,
+ .name = "afh_info",
+ },
+ [AFTCOL_CHUNKS] = {
+ .storage_type = OSL_DISK_STORAGE,
+ .name = "chunks",
+ }
+};
+
+static struct osl_table_description src_aft_desc = {
+ .name = "audio_files",
+ .num_columns = NUM_AFT_COLUMNS,
+ .flags = OSL_LARGE_TABLE,
+ .column_descriptions = src_aft_cols
+};
+
+static struct osl_table *src_aft, *dst_aft;
+
+static void open_src_aft(void)
+{
+ int ret;
+
+ PARA_NOTICE_LOG("opening: %s\n", src_aft_dir);
+ src_aft_desc.dir = src_db_dir;
+ ret = osl(osl_open_table(&src_aft_desc, &src_aft));
+ if (ret < 0) {
+ PARA_EMERG_LOG("can not open source audio file table: %s\n",
+ para_strerror(-ret));
+ exit(EXIT_FAILURE);
+ }
+ PARA_INFO_LOG("successfully opened source audio file table\n");
+}
+
+static int dst_aft_hash_compare(const struct osl_object *obj1,
+ const struct osl_object *obj2)
+{
+ return hash2_compare((unsigned char *)obj1->data,
+ (unsigned char *)obj2->data);
+}
+
+/* identical to src_aft_cols except the comparator and the hash size. */
+static struct osl_column_description dst_aft_cols[] = {
+ [AFTCOL_HASH] = {
+ .storage_type = OSL_MAPPED_STORAGE,
+ .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
+ .name = "hash",
+ .compare_function = dst_aft_hash_compare,
+ .data_size = HASH2_SIZE
+ },
+ [AFTCOL_PATH] = {
+ .storage_type = OSL_MAPPED_STORAGE,
+ .storage_flags = OSL_RBTREE | OSL_UNIQUE,
+ .name = "path",
+ .compare_function = string_compare,
+ },
+ [AFTCOL_AFSI] = {
+ .storage_type = OSL_MAPPED_STORAGE,
+ .storage_flags = OSL_FIXED_SIZE,
+ .name = "afs_info",
+ .data_size = AFSI_SIZE
+ },
+ [AFTCOL_AFHI] = {
+ .storage_type = OSL_MAPPED_STORAGE,
+ .name = "afh_info",
+ },
+ [AFTCOL_CHUNKS] = {
+ .storage_type = OSL_DISK_STORAGE,
+ .name = "chunks",
+ }
+};
+
+static struct osl_table_description dst_aft_desc = {
+ .name = "audio-files",
+ .num_columns = NUM_AFT_COLUMNS,
+ .flags = OSL_LARGE_TABLE,
+ .column_descriptions = dst_aft_cols
+};
+
+static int create_and_open_dst_aft(void)
+{
+ int ret;
+
+ PARA_NOTICE_LOG("creating %s\n", dst_aft_dir);
+ dst_aft_desc.dir = src_db_dir;
+ ret = osl(osl_create_table(&dst_aft_desc));
+ if (ret < 0) {
+ PARA_EMERG_LOG("could not create destination audio file table\n");
+ return ret;
+ }
+ ret = osl(osl_open_table(&dst_aft_desc, &dst_aft));
+ if (ret < 0) {
+ PARA_EMERG_LOG("could not open destination audio file table: %s\n",
+ para_strerror(-ret));
+ exit(EXIT_FAILURE);
+ }
+ PARA_INFO_LOG("successfully opened destination audio file table\n");
+ return 0;
+}
+
+static int copy_aft_row(struct osl_row *row, void *data)
+{
+ unsigned *n = data;
+ int i, ret;
+ unsigned char hash2[HASH2_SIZE] = "\0";
+ struct osl_object objs[NUM_AFT_COLUMNS] = {
+ [AFTCOL_HASH] = {.data = hash2, .size = HASH2_SIZE}
+ };
+
+ ret = osl(osl_open_disk_object(src_aft, row, AFTCOL_CHUNKS,
+ objs + AFTCOL_CHUNKS));
+ if (ret < 0) {
+ PARA_ERROR_LOG("can not open disk object: %s\n",
+ para_strerror(-ret));
+ return ret;
+ }
+ for (i = 0; i < NUM_AFT_COLUMNS; i++) {
+ if (i == AFTCOL_HASH) /* never assign to this index */
+ continue;
+ if (i == AFTCOL_CHUNKS) /* disk storage object handled above */
+ continue;
+ /* mapped storage */
+ ret = osl(osl_get_object(src_aft, row, i, objs + i));
+ if (ret < 0) {
+ PARA_ERROR_LOG("get_object (col = %d): %s\n",
+ i, para_strerror(-ret));
+ return ret;
+ }
+ if (i == AFTCOL_PATH)
+ PARA_DEBUG_LOG("copying %s\n", (char *)objs[i].data);
+ }
+ (*n)++;
+ memcpy(hash2, n, sizeof(*n));
+ ret = osl(osl_add_row(dst_aft, objs));
+ if (ret < 0)
+ PARA_ERROR_LOG("failed to add row: %s\n", para_strerror(-ret));
+ osl_close_disk_object(objs + AFTCOL_CHUNKS);
+ return ret;
+}
+
+static int convert_aft(void)
+{
+ unsigned n;
+ int ret;
+
+ osl_get_num_rows(src_aft, &n);
+ PARA_NOTICE_LOG("converting hash of %u rows to sha256\n", n);
+ n = 0;
+ ret = osl(osl_rbtree_loop(src_aft, AFTCOL_HASH, &n, copy_aft_row));
+ if (ret < 0)
+ PARA_ERROR_LOG("osl_rbtree_loop failed\n");
+ return ret;
+}
+
+static int remove_source_aft(void)
+{
+ pid_t pid;
+ int fds[3] = {-1, -1, -1}; /* no redirection of stdin/stdout/stderr */
+ int ret, wstatus;
+ char *cmdline = make_message("rm -rf %s", src_aft_dir);
+
+ PARA_NOTICE_LOG("removing %s\n", src_aft_dir);
+ ret = para_exec_cmdline_pid(&pid, cmdline, fds);
+ if (ret < 0) {
+ PARA_ERROR_LOG("exec failure\n");
+ goto out;
+ }
+ do {
+ ret = waitpid(pid, &wstatus, 0);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0)
+ PARA_ERROR_LOG("waitpid failure\n");
+out:
+ return ret;
+}
+
+static int rename_db(void)
+{
+ PARA_NOTICE_LOG("renaming %s -> %s\n", src_db_dir, dst_db_dir);
+ if (rename(src_db_dir, dst_db_dir) < 0) {
+ int ret = -ERRNO_TO_PARA_ERROR(errno);
+ PARA_ERROR_LOG("rename failed\n");
+ return ret;
+ }
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+ struct lls_parse_result *lpr; /* command line */
+ char *errctx;
+ int ret;
+
+ ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+ if (ret < 0)
+ goto out;
+ loglevel = OPT_UINT32_VAL(LOGLEVEL, lpr);
+ version_handle_flag("recv", OPT_GIVEN(VERSION, lpr));
+ handle_help_flag(lpr);
+ set_paths(lpr);
+ check_sanity();
+ open_src_aft();
+ ret = create_and_open_dst_aft();
+ if (ret < 0)
+ goto close_src_aft;
+ ret = convert_aft();
+ if (ret < 0)
+ goto close_dst_aft;
+ ret = remove_source_aft();
+ if (ret < 0)
+ goto close_dst_aft;
+ ret = rename_db();
+close_dst_aft:
+ osl_close_table(dst_aft, OSL_MARK_CLEAN);
+close_src_aft:
+ PARA_INFO_LOG("closing audio file tables\n");
+ osl_close_table(src_aft, OSL_MARK_CLEAN);
+out:
+ if (ret < 0) {
+ if (errctx)
+ PARA_ERROR_LOG("%s\n", errctx);
+ free(errctx);
+ PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ } else {
+ PARA_WARNING_LOG("success. Now start para_server and force-add"
+ " all audio files.\n");
+ }
+ return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/vss.c b/vss.c
index 235219c1..f9bf57b5 100644
--- a/vss.c
+++ b/vss.c
@@ -348,7 +348,7 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
}
static int vss_get_chunk(int chunk_num, struct vss_task *vsst,
- char **buf, size_t *sz)
+ char **buf, uint32_t *len)
{
int ret;
@@ -363,15 +363,15 @@ static int vss_get_chunk(int chunk_num, struct vss_task *vsst,
if (chunk_num == 0 && vsst->header_len > 0) {
assert(vsst->header_buf);
*buf = vsst->header_buf; /* stripped header */
- *sz = vsst->header_len;
+ *len = vsst->header_len;
return 0;
}
ret = afh_get_chunk(chunk_num, &mmd->afd.afhi,
mmd->afd.audio_format_id, vsst->map, vsst->mapsize,
- (const char **)buf, sz, &vsst->afh_context);
+ (const char **)buf, len, &vsst->afh_context);
if (ret < 0) {
*buf = NULL;
- *sz = 0;
+ *len = 0;
}
return ret;
}
@@ -380,7 +380,7 @@ static int compute_group_size(struct vss_task *vsst, struct fec_group *g,
int max_bytes)
{
char *buf;
- size_t len;
+ uint32_t len;
int ret, i, max_chunks = PARA_MAX(1LU, 150 / tv2ms(vss_chunk_time()));
if (g->first_chunk == 0) {
@@ -587,7 +587,7 @@ static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
slice_copied = 0;
for (c = g->first_chunk; c < g->first_chunk + g->num_chunks; c++) {
char *buf;
- size_t src_len;
+ uint32_t src_len;
ret = vss_get_chunk(c, vsst, &buf, &src_len);
if (ret < 0)
return ret;
@@ -1029,7 +1029,7 @@ static void vss_send(struct vss_task *vsst)
struct timeval due;
struct fec_client *fc, *tmp_fc;
char *buf;
- size_t len;
+ uint32_t len;
if (!vsst->map || !vss_playing())
return;
@@ -1139,7 +1139,7 @@ static int vss_post_select(struct sched *s, void *context)
if (vsst->afsss != AFS_SOCKET_CHECK_FOR_WRITE)
recv_afs_result(vsst, &s->rfds);
else if (FD_ISSET(vsst->afs_socket, &s->wfds)) {
- PARA_NOTICE_LOG("requesting new fd from afs\n");
+ PARA_INFO_LOG("requesting new fd from afs\n");
ret = write_buffer(vsst->afs_socket, "new");
if (ret < 0)
PARA_CRIT_LOG("%s\n", para_strerror(-ret));
@@ -1177,7 +1177,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();
@@ -1208,11 +1208,14 @@ void vss_init(int afs_socket, struct sched *s)
void vss_shutdown(void)
{
int i;
+ bool is_command_handler = process_is_command_handler();
FOR_EACH_SENDER(i) {
if (!senders[i]->shutdown)
continue;
- PARA_NOTICE_LOG("shutting down %s sender\n", senders[i]->name);
+ if (!is_command_handler)
+ PARA_NOTICE_LOG("shutting down %s sender\n",
+ senders[i]->name);
senders[i]->shutdown();
}
}
diff --git a/web/about.in.html b/web/about.in.html
index 8395864a..f188ff25 100644
--- a/web/about.in.html
+++ b/web/about.in.html
@@ -1,8 +1,8 @@
About
-Paraslash is a collection of network audio streaming tools for Unix
-systems. It is written in C and released under the GPLv2.
+ Paraslash is a collection of network audio streaming tools for
+Unix systems. It is written in C and released under the GPLv2.
- Runs on Linux, FreeBSD, NetBSD
@@ -10,15 +10,17 @@ systems. It is written in C and released under the GPLv2.
- http, dccp and udp network streaming
- Stand-alone decoder, player, tagger
- Curses-based gui (screenshot)
- - Integrated volume normalizer, fader, alarm clock
+ - Volume normalizer, fader, alarm clock
- Sophisticated audio file selector
- Command line interface with tab-completion
- Open source and well documented
- Author: André Noll,
-maan@tuebingen.mpg.de,
-Homepage: http://people.tuebingen.mpg.de/maan/
-
-Comments and bug reports are welcome. Please provide the version of
-paraslash you are using and relevant parts of the logs.
+ Author: Andre Noll, maan@tuebingen.mpg.de,
+Homepage: http://people.tuebingen.mpg.de/maan/
+
+
+ Comments and bug reports are welcome. Please provide the version
+of paraslash you are using and relevant parts of the logs.
diff --git a/web/documentation.in.html b/web/documentation.in.html
index d6d690a1..32efe9c3 100644
--- a/web/documentation.in.html
+++ b/web/documentation.in.html
@@ -23,12 +23,13 @@
[para_filter]
[para_write]
[para_gui]
- [para_mixere]
+ [para_mixer]
[para_play]
+ [para_upgrade_db]
Source code documentation
diff --git a/web/download.in.html b/web/download.in.html
index 9ef92b7a..2b31e5d7 100644
--- a/web/download.in.html
+++ b/web/download.in.html
@@ -1,8 +1,9 @@
Download
-Paraslash is only available as source code, no binary packages are
-provided at this point. There are several ways to download the source:
+ Paraslash is only available as source code, no binary packages
+are provided at this point. There are several ways to download the
+source:
- git.
@@ -19,7 +20,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/footer.html b/web/footer.html
index 38572c94..cceeff1a 100644
--- a/web/footer.html
+++ b/web/footer.html
@@ -1,5 +1,3 @@
-
-