A single urgent fix for a server crash.
* maint:
vss: Reset afhi.chunks_total on eof.
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@
crypto_ldflags := @crypto_ldflags@
iconv_ldflags := @iconv_ldflags@
+ENABLE_UBSAN := @ENABLE_UBSAN@
+
include Makefile.real
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)
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)
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))
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
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 \
para_play \
para_recv \
para_server \
+para_upgrade_db \
para_write \
: LDFLAGS += $(lopsub_ldflags)
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"
--------------------------------------
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;
*/
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;
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)
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);
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) {
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);
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);
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);
* 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);
/**
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);
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];
*/
__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];
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);
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;
}
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;
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,
/**
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.
*/
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) {
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);
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);
}
}
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();
*/
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;
.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,
};
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
*/
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));
}
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);
*/
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;
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) {
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)
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;
}
}
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;
}
}
/** 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),
};
/*
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);
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);
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;
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)
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);
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)
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;
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.
snd_pcm_drain(pad->handle);
snd_pcm_close(pad->handle);
snd_config_update_free_global();
+free_pad:
free(pad);
}
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;
}
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;
}
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;
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)
{
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;
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);
}
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;
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;
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);
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;
I9E_DUMMY_COMPLETER(stop);
I9E_DUMMY_COMPLETER(addatt);
I9E_DUMMY_COMPLETER(init);
-I9E_DUMMY_COMPLETER(tasks);
static struct i9e_completer completers[];
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.
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
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)
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;
#include <sys/socket.h>
#include <regex.h>
#include <signal.h>
-#include <sys/types.h>
-#include <osl.h>
#include <arpa/inet.h>
-#include <sys/un.h>
#include <netdb.h>
#include <lopsub.h>
#include "command.h"
#include "string.h"
#include "afh.h"
-#include "afs.h"
#include "net.h"
#include "server.h"
#include "list.h"
}
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);
}
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,
*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;
{
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;
/* 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;
* 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);
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
daemon
http_send
close_on_fork
- mm
crypt_common
base64
ipc
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"
faad: $HAVE_FAAD
audio format handlers: $audio_format_handlers
+exe: $executables
para_server: $build_server
para_gui: $build_gui
para_mixer: $build_mixer
* 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);
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.
*
}
/**
- * 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)
{
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"), \
PARA_ERROR(OGGDEC_VERSION, "vorbis version mismatch"), \
PARA_ERROR(OGG_EMPTY, "no ogg pages found"), \
PARA_ERROR(OGG_PACKET_IN, "ogg_stream_packetin() failed"), \
- PARA_ERROR(OGG_STREAM_FLUSH, "ogg_stream_flush() failed"), \
PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \
PARA_ERROR(OPENSSH_PARSE, "could not parse openssh private key"), \
PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \
* 'E_') and gets later redefined to expand to the error text only
*/
#define PARA_ERROR(err, msg) E_ ## err
+/**
+ * Numeric error codes.
+ *
+ * Public functions which can fail return the negated value of one of the
+ * constants defined here to indicate the cause of the error.
+ *
+ * \sa \ref para_strerror().
+ */
enum para_error_codes {PARA_ERRORS};
#undef PARA_ERROR
#define PARA_ERROR(err, msg) msg
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);
* 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)
{
* 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;
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,...);
* Copied from the Linux kernel source tree, version 2.6.13.
*
* Licensed under the GPL v2 as per the whole kernel source tree.
- *
*/
-/** \file list.h doubly linked list implementation */
+/** \file list.h Doubly linked list implementation. */
#include <stddef.h> /* offsetof */
-/** get the struct this entry is embedded in */
+/** Get the struct this entry is embedded in. */
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
-/**
- * Non-NULL pointers that will result in page faults under normal
- * circumstances, used to verify that nobody uses non-initialized list entries.
- * Used for poisoning the \a next pointer of struct list_head.
- */
-#define LIST_POISON1 ((void *) 0x00100100)
-/** Non-null pointer, used for poisoning the \a prev pointer of struct
- * list_head
- */
-#define LIST_POISON2 ((void *) 0x00200200)
-
-/** Simple doubly linked list implementation. */
+/** A list head is just a pair of pointers. */
struct list_head {
- /** pointer to the next list entry */
+ /** Pointer to the next list entry. */
struct list_head *next;
- /** pointer to the previous list entry */
+ /** Pointer to the previous list entry. */
struct list_head *prev;
};
/** Define an initialized list head. */
-#define INITIALIZED_LIST_HEAD(name) struct list_head name = { &(name), &(name) }
-
-
-/** must be called before using any other list functions */
-#define INIT_LIST_HEAD(ptr) do { \
- (ptr)->next = (ptr); (ptr)->prev = (ptr); \
-} while (0)
+#define INITIALIZED_LIST_HEAD(name) struct list_head name = {&(name), &(name)}
-
-/*
- * Some of the internal functions ("__xxx") are useful when
- * manipulating whole lists rather than single entries, as
- * sometimes we already know the next/prev entries and we can
- * generate better code by using them directly rather than
- * using the generic single-entry routines.
- */
-
-
-/*
- * Insert a new entry between two known consecutive entries.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
-static inline void __list_add(struct list_head *new,
- struct list_head *prev,
- struct list_head *next)
+/** This must be called before using any other list functions. */
+static inline void init_list_head(struct list_head *head)
{
- next->prev = new;
- new->next = next;
- new->prev = prev;
- prev->next = new;
+ head->next = head;
+ head->prev = head;
}
/**
- * add a new entry
+ * Insert a new entry after the specified head.
*
- * \param new new entry to be added
- * \param head list head to add it after
+ * \param entry The new entry to add.
+ * \param head The list head to add it after.
*
- * Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
-static inline void para_list_add(struct list_head *new, struct list_head *head)
+static inline void para_list_add(struct list_head *entry, struct list_head *head)
{
- __list_add(new, head, head->next);
+ entry->prev = head;
+ entry->next = head->next;
+ head->next->prev = entry;
+ head->next = entry;
}
/**
- * add a new entry
+ * Insert a new entry before the specified head.
*
- * \param new new entry to be added
- * \param head list head to add it before
+ * \param entry The new entry to add.
+ * \param head list head to add it before.
*
- * Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
-static inline void list_add_tail(struct list_head *new, struct list_head *head)
+static inline void list_add_tail(struct list_head *entry, struct list_head *head)
{
- __list_add(new, head->prev, head);
+ entry->prev = head->prev;
+ entry->next = head;
+ head->prev->next = entry;
+ head->prev = entry;
}
-/*
- * Delete a list entry by making the prev/next entries
- * point to each other.
+/**
+ * Delete an entry from a list.
+ *
+ * \param entry The element to delete.
*
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
+ * The list entry is in an undefined state after this and \ref list_empty()
+ * does not return true.
*/
-static inline void __list_del(struct list_head * prev, struct list_head * next)
+static inline void list_del(struct list_head *entry)
{
- next->prev = prev;
- prev->next = next;
+ entry->prev->next = entry->next;
+ entry->next->prev = entry->prev;
+ /*
+ * These non-NULL pointers result in page faults when dereferenced.
+ * This helps to catch bugs resulting from using deleted list heads.
+ */
+ entry->next = (void *)0x00100100;
+ entry->prev = (void *)0x00200200;
}
/**
- * Delete entry from list.
- *
- * \param entry the element to delete from the list.
+ * Delete an entry from one list and add it as another list's head.
*
- * Note: list_empty on entry does not return true after this, the entry is
- * in an undefined state.
+ * \param entry The entry to move.
+ * \param head The head that will precede our entry.
*/
-static inline void list_del(struct list_head *entry)
+static inline void list_move(struct list_head *entry, struct list_head *head)
{
- __list_del(entry->prev, entry->next);
- entry->next = LIST_POISON1;
- entry->prev = LIST_POISON2;
+ list_del(entry);
+ para_list_add(entry, head);
}
/**
- * delete from one list and add as another's head
+ * Test whether a list contains no entries.
*
- * \param list: the entry to move
- * \param head: the head that will precede our entry
+ * \param head The list to test.
*/
-static inline void list_move(struct list_head *list, struct list_head *head)
+static inline int list_empty(const struct list_head *head)
{
- __list_del(list->prev, list->next);
- para_list_add(list, head);
+ return head->next == head;
}
/**
- * test whether a list is empty
+ * Test whether a list has just one entry.
*
- * \param head the list to test.
+ * \param head The list to test.
*/
-static inline int list_empty(const struct list_head *head)
+static inline int list_is_singular(const struct list_head *head)
{
- return head->next == head;
+ return !list_empty(head) && (head->next == head->prev);
}
/**
- * get the struct for this entry
+ * Get the struct in which this entry is embedded in.
*
- * \param ptr the &struct list_head pointer.
- * \param type the type of the struct this is embedded in.
- * \param member the name of the list_struct within the struct.
+ * \param ptr The list head pointer.
+ * \param type The type of containing structure.
+ * \param member The name of the list head member within the structure.
*/
-#define list_entry(ptr, type, member) \
- container_of(ptr, type, member)
+#define list_entry(ptr, type, member) container_of(ptr, type, member)
/**
- * iterate over list of given type
+ * Iterate over a list.
*
- * \param pos the type * to use as a loop counter.
- * \param head the head for your list.
- * \param member the name of the list_struct within the struct.
+ * \param pos A struct pointer which serves as the iterator.
+ * \param head The head of the list.
+ * \param member The name of the list head member within the structure.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
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)
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
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
--- /dev/null
+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]
+++ /dev/null
-/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file mm.c Paraslash's mood methods. */
-
-#include <regex.h>
-#include <fnmatch.h>
-#include <osl.h>
-#include <lopsub.h>
-
-#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}
-};
+++ /dev/null
-/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, 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[];
#include "afh.h"
#include "afs.h"
#include "list.h"
-#include "mm.h"
#include "mood.h"
/*
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;
};
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);
}
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;
-};
-
-/*
- * <accept [with score <score>] | deny [with score <score>] | score <score>>
- * [if] [not] <mood_method> [options]
- * <score> 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)
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) {
}
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);
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)
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)
assert(n);
if (n == 1) {
memset(&statistics, 0, sizeof(statistics));
+ statistics.normalization_divisor = 1;
return 1;
}
/*
* 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);
}
/**
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;
if (ret < 0)
return ret;
- score = compute_score(&afsi, mood_score);
+ score = compute_score(&afsi);
return score_add(aft_row, score);
}
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);
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);
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)
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?
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);
}
/**
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;
}
/**
.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;
*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(
goto out;
}
}
- log_statistics();
ret = statistics.num;
out:
free(aa.array);
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.
*
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 */
{
struct flowopts *new = para_malloc(sizeof(*new));
- INIT_LIST_HEAD(&new->sockopts);
+ init_list_head(&new->sockopts);
return new;
}
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);
+}
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;
if (btr_get_input_queue_size(pt->btrn) > 0)
return;
}
- ie9_print_status_bar(str, len);
+ i9e_print_status_bar(str, len);
}
/*
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);
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)
{
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;
* \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)
* \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)
{
*
* \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().
*/
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 */
*/
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);
}
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];
/** \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 <netinet/in.h>
#include <sys/socket.h>
#include <signal.h>
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(
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))
{
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()) {
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);
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)
/* 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))
--- /dev/null
+/* Copyright (C) 2020 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file upgrade_db.c Prepare the paraslash database for paraslash-0.7. */
+
+#include <osl.h>
+#include <lopsub.h>
+#include <regex.h>
+
+#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;
+}
}
static int vss_get_chunk(int chunk_num, struct vss_task *vsst,
- char **buf, size_t *sz)
+ char **buf, uint32_t *len)
{
int ret;
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;
}
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) {
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;
struct timeval due;
struct fec_client *fc, *tmp_fc;
char *buf;
- size_t len;
+ uint32_t len;
if (!vsst->map || !vss_playing())
return;
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));
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();
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();
}
}
<h1>About</h1>
<hr>
-Paraslash is a collection of network audio streaming tools for Unix
-systems. It is written in C and released under the GPLv2.
+<p> Paraslash is a collection of network audio streaming tools for
+Unix systems. It is written in C and released under the GPLv2. </p>
<ul>
<li> Runs on Linux, FreeBSD, NetBSD </li>
<li> http, dccp and udp network streaming </li>
<li> Stand-alone decoder, player, tagger </li>
<li> Curses-based gui (<a href="gui.png">screenshot</a>) </li>
- <li> Integrated volume normalizer, fader, alarm clock </li>
+ <li> Volume normalizer, fader, alarm clock </li>
<li> Sophisticated audio file selector </li>
<li> Command line interface with tab-completion </li>
<li> Open source and well documented </li>
</ul>
-<b> Author: </b> André Noll,
-<a href="mailto:maan@tuebingen.mpg.de">maan@tuebingen.mpg.de</a>,
-Homepage: <a href="http://people.tuebingen.mpg.de/maan/">http://people.tuebingen.mpg.de/maan/</a>
-<br>
-Comments and bug reports are welcome. Please provide the version of
-paraslash you are using and relevant parts of the logs.
+<p> Author: Andre Noll, <a
+href="mailto:maan@tuebingen.mpg.de">maan@tuebingen.mpg.de</a>,
+Homepage: <a
+href="http://people.tuebingen.mpg.de/maan/">http://people.tuebingen.mpg.de/maan/</a>
+</p>
+
+<p> Comments and bug reports are welcome. Please provide the version
+of paraslash you are using and relevant parts of the logs. </p>
[<a href="para_filter.man.html">para_filter</a>]
[<a href="para_write.man.html">para_write</a>]
[<a href="para_gui.man.html">para_gui</a>]
- [<a href="para_mixer.man.html">para_mixere</a>]
+ [<a href="para_mixer.man.html">para_mixer</a>]
[<a href="para_play.man.html">para_play</a>]
+ [<a href="para_upgrade_db.man.html">para_upgrade_db</a>]
</p>
<h2> Source code documentation </h2>
<ul>
- <li> <a href="doxygen/html/index.html">API Reference</a> </li>
+ <li> <a href="doxygen/html/files.html">API Reference</a> </li>
</ul>
<h1>Download</h1>
<hr>
-Paraslash is only available as source code, no binary packages are
-provided at this point. There are several ways to download the source:
+<p> Paraslash is only available as source code, no binary packages
+are provided at this point. There are several ways to download the
+source: </p>
<ul>
<li> <em> git</em>.
checkout of any of the four integration branches maint,
master, next, pu (see the
- <a href="manual.html#Git.branches">Git branches</a>
+ <a href="manual.html#Git-branches">Git branches</a>
section of the manual). All previous releases
correspond to tagged commits and may be checked out
<hr>
- </td>
- </table>
</body>
</html>
<body>
<table>
<tr>
- <td>
- <a title="paraslash homepage" href=".">
- <img src="paraslash.png" alt="paraslash">
+ <td rowspan="2">
+ <a title="paraslash homepage" href="./index.html">
+ <img src="paraslash.svg" alt="paraslash">
</a>
</td>
<td>
- <h3>Paraslash network audio streaming tools</h3>
+ <span class="slogan">Paraslash Audio Streaming</span>
</td>
</tr>
<tr>
- <td valign="top">
- <br>
- <a href=".">About</a><br>
- <a href="news.html">News</a><br>
- <a href="download.html">Download</a><br>
- <a href="documentation.html">Documentation</a><br>
- <a href="devel.html">Development</a><br>
- </td>
<td>
+ <a href="./index.html">About</a>
+ <a href="news.html">News</a>
+ <a href="download.html">Download</a>
+ <a href="documentation.html">Documentation</a>
+ <a href="devel.html">Development</a>
+ </td>
+ </tr>
+ </table>
+<hr>
<body>
<table>
<tr>
- <td>
- <a title="paraslash homepage" href="../..//">
- <img src="../../paraslash.png" alt="paraslash">
+ <td rowspan="2">
+ <a title="paraslash homepage" href="../../index.html">
+ <img src="../../paraslash.svg" alt="paraslash">
</a>
</td>
<td>
- <h3>Paraslash network audio streaming tools</h3>
+ <span class="slogan">Paraslash Audio Streaming</span>
</td>
</tr>
<tr>
- <td valign="top">
- <br>
- <a href="../..">About</a><br>
- <a href="../../news.html">News</a><br>
- <a href="../../download.html">Download</a><br>
- <a href="../../documentation.html">Documentation</a><br>
- <a href="../../devel.html">Development</a><br>
- </td>
<td>
- <h1>API Reference</h1>
- <hr />
-
+ <a href="../../index.html">About</a>
+ <a href="../../news.html">News</a>
+ <a href="../../download.html">Download</a>
+ <a href="../../documentation.html">Documentation</a>
+ <a href="../../devel.html">Development</a>
+ </td>
+ </tr>
+ </table>
+<hr>
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="80" height="100"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+>
+ <defs>
+ <radialGradient id="gradient" r="70%">
+ <stop offset="0%" stop-color="#d40"/>
+ <stop offset="100%" stop-color="#010"/>
+ </radialGradient>
+
+ <g
+ id="bow"
+ stroke="#111" stroke-width="0"
+ fill="url(#gradient)"
+ >
+ <path d="M 40,50 c 0,-21 30,-21 30,0"/>
+ </g>
+ </defs>
+ <g transform="scale(1.0, 1.3) translate(0, -11)">
+ <circle
+ cx="40" cy="50" r="38"
+ stroke-width="0"
+ fill="url(#gradient)"
+ />
+ <use
+ xlink:href="#bow"
+ transform="
+ scale(1.5)
+ rotate(320, 40,50)
+ translate(-14, -24)
+ "
+ />
+ <use
+ xlink:href="#bow"
+ transform="
+ translate(0, -36)
+ scale(1.5)
+ rotate(140, 40,50)
+ "
+ />
+ </g>
+</svg>
para_client init
This initializes a couple of empty tables under
-~/.paraslash/afs_database-0.4. You normally don't need to look at these
+~/.paraslash/afs_database-0.7. You normally don't need to look at these
tables, but it's good to know that you can start from scratch with
- rm -rf ~/.paraslash/afs_database-0.4
+ rm -rf ~/.paraslash/afs_database-0.7
in case something went wrong.
- para_client receives the encrypted buffer and decrypts it with the
user's private key, thereby obtaining the challenge buffer and the
-session key. It sends the SHA1 hash value of the challenge back to
-para_server and stores the session key for further use.
+session key. It hashes the challenge buffer with a crytographic hash
+function, sends the hash value back to para_server and stores the
+session key for further use.
-- para_server also computes the SHA1 hash of the challenge and compares
+- para_server also computes the hash value of the challenge and compares
it against what was sent back by the client.
- If the two hashes do not match, the authentication has failed and
paraslash relies on the quality of the pseudo-random bytes provided
by the crypto library (openssl or libgcrypt), on the security of
the implementation of the RSA and AES crypto routines and on the
-infeasibility to invert the SHA1 function.
+infeasibility to invert the hash function.
Neither para_server or para_client create RSA keys on their
own. This has to be done once for each user as sketched in
AFS database. It contains the information needed to stream each audio
file. In particular the following data is stored for each audio file.
-- SHA1 hash value of the audio file contents. This is computed once
-when the file is added to the database. Whenever AFS selects this
-audio file for streaming the hash value is recomputed and checked
-against the value stored in the database to detect content changes.
+- The cryptographic hash value of the audio file contents. This is
+computed once when the file is added to the database. Whenever AFS
+selects this audio file for streaming the hash value is recomputed
+and checked against the value stored in the database to detect
+content changes.
- The time when this audio file was last played.
`bitrate` | integer | The average bitrate
`frequency` | integer | The output sample rate
`channels` | integer | The number of channels
+`duration` | integer | The number of milliseconds
`is_set("foo")` | boolean | True if attribute "foo" is set.
[\*] For most audio formats, the year tag is stored as a string. It
File renames and content changes
--------------------------------
-Since the audio file selector knows the SHA1 of each audio file that
+Since the audio file selector knows the hash of each audio file that
has been added to the afs database, it recognizes if the content of
a file has changed, e.g. because an ID3 tag was added or modified.
Also, if a file has been renamed or moved to a different location,
case you'll have to run the oslfsck program of libosl to fix your
database:
- oslfsck -fd ~/.paraslash/afs_database-0.4
+ oslfsck -fd ~/.paraslash/afs_database-0.7
However, make sure para_server isn't running before executing oslfsck.
If you don't mind to recreate your database you can start
from scratch by removing the entire database directory, i.e.
- rm -rf ~/.paraslash/afs_database-0.4
+ rm -rf ~/.paraslash/afs_database-0.7
Be aware that this removes all attribute definitions, all playlists
and all mood definitions and requires to re-initialize the tables.
- Don't leave whitespace at the end of lines.
- The limit on the length of lines is 80 columns.
- Use K&R style for placing braces and spaces:
-
+<pre>
if (x is true) {
we do y
}
-
+</pre>
- Use a space after (most) keywords.
- Do not add spaces around (inside) parenthesized expressions.
- Use one space around (on each side of) most binary and ternary operators.
-body,h1,h2,h3,h4,h5,h6,p,center,td,th,ul,dl,div {
- font-family: sans-serif;
-}
-
body {
+ font-family: sans-serif;
background-color: black;
color: #bbbbbb;
- margin: 0px;
-}
-
-table {
- padding: 8px 4px;
-}
-
-th {
- padding: 2px 5px;
- font-size: 100%;
- text-align: left;
+ margin: 20px;
}
td {
- padding: 2px 5px;
- font-size: 100%;
+ padding: 2px 10px 2px 10px;
vertical-align: top;
}
+span.slogan {
+ font-size: 200%;
+ font-weight: bold;
+ color: #ddd;
+}
+
a {
color: #cc3322;
}
hr {
- height: 1px;
- border: none;
border-top: 1px solid yellow;
}
-img {
- float: right;
- border-width: 0px;
-}
-
-caption { font-weight: bold }
-
/* doxgen */
/* Data structure index. Box with clickable letters */
const char *writer_name(int wid);
void register_writer_node(struct writer_node *wn, struct btr_node *parent,
struct sched *s);
-void get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
-void get_btr_channels(struct btr_node *btrn, int32_t *result);
-void get_btr_sample_format(struct btr_node *btrn, int32_t *result);
+int get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
+int get_btr_channels(struct btr_node *btrn, int32_t *result);
+int get_btr_sample_format(struct btr_node *btrn, int32_t *result);
void print_writer_helps(bool detailed);
}
}
-static void get_btr_value(struct btr_node *btrn, const char *cmd,
+static int get_btr_value(struct btr_node *btrn, const char *cmd,
int32_t *result)
{
char *buf = NULL;
int ret = btr_exec_up(btrn, cmd, &buf);
- if (ret < 0) {
- /*
- * This really should not happen. It means one of our parent
- * nodes died unexpectedly. Proceed with fingers crossed.
- */
- PARA_CRIT_LOG("cmd %s: %s\n", cmd, para_strerror(-ret));
- *result = 0;
- return;
- }
+ *result = 0;
+ /*
+ * Errors may happen when the decoder returns EOF before the writer had
+ * a chance to query the buffer tree for the channel count, sample rate
+ * etc.
+ */
+ if (ret < 0)
+ return ret;
ret = para_atoi32(buf, result);
assert(ret >= 0);
free(buf);
+ return ret;
}
/**
* \param btrn Where to start the search.
* \param result Filled in by this function.
*
- * This function is assumed to succeed and terminates on errors.
+ * \return Standard.
*/
-void get_btr_sample_rate(struct btr_node *btrn, int32_t *result)
+int get_btr_sample_rate(struct btr_node *btrn, int32_t *result)
{
- get_btr_value(btrn, "sample_rate", result);
+ return get_btr_value(btrn, "sample_rate", result);
}
/**
*
* \param btrn See \ref get_btr_sample_rate.
* \param result See \ref get_btr_sample_rate.
+ *
+ * \return Standard.
*/
-void get_btr_channels(struct btr_node *btrn, int32_t *result)
+int get_btr_channels(struct btr_node *btrn, int32_t *result)
{
- get_btr_value(btrn, "channels", result);
+ return get_btr_value(btrn, "channels", result);
}
/**
*
* \param btrn See \ref get_btr_sample_rate.
* \param result Contains the sample format as an enum sample_format type.
+ *
+ * \return Standard.
*/
-void get_btr_sample_format(struct btr_node *btrn, int32_t *result)
+int get_btr_sample_format(struct btr_node *btrn, int32_t *result)
{
- get_btr_value(btrn, "sample_format", result);
+ return get_btr_value(btrn, "sample_format", result);
}
bitrate {return BITRATE;}
frequency {return FREQUENCY;}
channels {return CHANNELS;}
+duration {return DURATION;}
true {return TRUE;}
false {return FALSE;}
case CHANNELS:
result->intval= mp_channels(ctx);
return ST_INTVAL;
+ case DURATION:
+ result->intval= mp_duration(ctx);
+ return ST_INTVAL;
/* bools */
case IS_SET:
arg = node->children[0]->sv.strval;
%token <node> BITRATE
%token <node> FREQUENCY
%token <node> CHANNELS
+%token <node> DURATION
%token <node> FALSE TRUE
/* keywords without semantic value */
| BITRATE {$$ = mp_new_ast_leaf_node(BITRATE);}
| FREQUENCY {$$ = mp_new_ast_leaf_node(FREQUENCY);}
| CHANNELS {$$ = mp_new_ast_leaf_node(CHANNELS);}
+ | DURATION {$$ = mp_new_ast_leaf_node(DURATION);}
;
boolexp: IS_SET '(' STRING_LITERAL ')' {$$ = ast_node_new_unary(IS_SET, $3);}