Was cooking in next for three weeks.
* web: Remove screenshots, license and credits page.
* web: Integrate Contact page into about.
* web: Make FEATURES the new homepage, rename it to "about".
* web: Shorten feature list
- On Linux systems, local sockets are now created in the
abstract name space by default. This allows to get rid of
the socket specials in /var/paraslash.
+ - The --user-allow option of para_audiod now accepts also
+ usernames rather than only user IDs.
- New autoconf macros to avoid duplication in configure.ac.
+ - Status items (as shown by para_gui) are updated correctly
+ when the meta information of the current audio changes.
+ - para_server and para_audiod no longer refuse to start in
+ the background if no log file is given. Instead, all log
+ messages go to /dev/null in this case.
+ - Web page cleanup.
Download: ./releases/paraslash-git.tar.bz2 (tarball)
+
------------------------------------------
0.5.4 (2015-01-23) "exponential alignment"
------------------------------------------
}
/**
- * Open the audio file with highest score.
+ * Pass the fd of the next audio file to the server process.
*
* This stores all information for streaming the "best" audio file in a shared
* memory area. The id of that area and an open file descriptor for the next
*/
static int open_next_audio_file(void)
{
- struct osl_row *aft_row;
struct audio_file_data afd;
int ret, shmid;
char buf[8];
- long score;
-again:
- PARA_NOTICE_LOG("getting next audio file\n");
- ret = score_get_best(&aft_row, &score);
+
+ ret = open_and_update_audio_file(&afd);
if (ret < 0) {
PARA_ERROR_LOG("%s\n", para_strerror(-ret));
goto no_admissible_files;
}
- ret = open_and_update_audio_file(aft_row, score, &afd);
- if (ret < 0) {
- ret = score_delete(aft_row);
- if (ret < 0) {
- PARA_ERROR_LOG("%s\n", para_strerror(-ret));
- goto no_admissible_files;
- }
- goto again;
- }
shmid = ret;
if (!write_ok(server_socket)) {
ret = -E_AFS_SOCKET;
{
uint32_t table_mask = *(uint32_t *)query->data;
int i, ret;
- struct para_buffer pb = {.buf = NULL};
+ struct para_buffer pb = {
+ .max_size = shm_get_shmmax(),
+ .private_data = &(struct afs_max_size_handler_data) {
+ .fd = fd,
+ .band = SBD_OUTPUT
+ }
+ };
close_afs_tables();
for (i = 0; i < NUM_AFS_TABLES; i++) {
/* aft */
void aft_init(struct afs_table *t);
int aft_get_row_of_path(const char *path, struct osl_row **row);
-int open_and_update_audio_file(struct osl_row *aft_row, long score,
- struct audio_file_data *afd);
+int open_and_update_audio_file(struct audio_file_data *afd);
int load_afd(int shmid, struct audio_file_data *afd);
int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi);
int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi);
.column_descriptions = aft_cols
};
-/* We don't want dot or dot-dot anywhere. */
-static int verify_dotfile(const char *rest)
-{
- /*
- * The first character was '.', but that has already been discarded, we
- * now test the rest.
- */
- switch (*rest) {
- case '\0': case '/': /* /foo/. and /foo/./bar are not ok */
- return -1;
- case '.': /* path start with /foo/.. */
- if (rest[1] == '\0' || rest[1] == '/')
- return -1; /* /foo/.. or /foo/../bar are not ok */
- /* /foo/..bar is ok */
- }
- return 1;
-}
-
/*
- * We fundamentally don't like some paths: We don't want double slashes or
- * slashes at the end that can make pathnames ambiguous.
+ * Produce a canonicalized absolute pathname.
+ *
+ * Returns one if the resolved path a directory, zero if it is a regular file,
+ * negative on errors.
*/
static int verify_path(const char *orig_path, char **resolved_path)
{
- char c;
- size_t len;
- char *path;
+ int ret;
+ char *path = NULL;
+ struct stat statbuf;
if (*orig_path != '/') /* we only accept absolute paths */
- return -E_BAD_PATH;
- len = strlen(orig_path);
- *resolved_path = para_strdup(orig_path);
- path = *resolved_path;
- while (len > 1 && path[--len] == '/')
- path[len] = '\0'; /* remove slash at the end */
- c = *path++;
- while (c) {
- if (c == '/') {
- c = *path++;
- switch (c) {
- case '/': /* double slash */
- goto bad_path;
- case '.':
- if (verify_dotfile(path) < 0)
- goto bad_path;
- default:
- continue;
- }
- }
- c = *path++;
- }
- return 1;
-bad_path:
- free(*resolved_path);
+ goto fail;
+ path = realpath(orig_path, NULL);
+ if (!path)
+ goto fail;
+ if (stat(path, &statbuf) < 0)
+ goto fail;
+ if (S_ISREG(statbuf.st_mode))
+ ret = 0;
+ else if (S_ISDIR(statbuf.st_mode))
+ ret = 1;
+ else
+ goto fail;
+ *resolved_path = path;
+ return ret;
+fail:
+ *resolved_path = NULL;
+ free(path);
return -E_BAD_PATH;
}
return ret;
}
-static int make_status_items(struct audio_file_data *afd,
- struct afs_info *afsi, char *path, long score,
- unsigned char *hash)
+static struct ls_data status_item_ls_data;
+static struct osl_row *current_aft_row;
+
+static void make_inode_status_items(struct para_buffer *pb)
+{
+ struct stat statbuf = {.st_size = 0};
+ char *path, mtime_str[30] = "\0";
+ struct tm mtime_tm;
+ int ret;
+
+ ret = get_audio_file_path_of_row(current_aft_row, &path);
+ if (ret < 0)
+ goto out;
+ ret = stat(path, &statbuf);
+ if (ret < 0)
+ goto out;
+ localtime_r(&statbuf.st_mtime, &mtime_tm);
+ ret = strftime(mtime_str, 29, "%b %d %Y", &mtime_tm);
+ assert(ret > 0); /* number of bytes placed in mtime_str */
+out:
+ /* We don't care too much about errors here */
+ (void)WRITE_STATUS_ITEM(pb, SI_MTIME, "%s\n", mtime_str);
+ (void)WRITE_STATUS_ITEM(pb, SI_FILE_SIZE, "%ld\n", statbuf.st_size / 1024);
+}
+
+static int make_status_items(void)
{
- struct ls_data d = {
- .afhi = afd->afhi,
- .afsi = *afsi,
- .path = path,
- .score = score,
- .hash = hash
- };
struct ls_options opts = {
.flags = LS_FLAG_FULL_PATH | LS_FLAG_ADMISSIBLE_ONLY,
.mode = LS_MODE_VERBOSE,
int ret;
time(¤t_time);
- ret = print_list_item(&d, &opts, &pb, current_time);
+ ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time);
if (ret < 0)
return ret;
+ make_inode_status_items(&pb);
free(status_items);
status_items = pb.buf;
memset(&pb, 0, sizeof(pb));
pb.max_size = shm_get_shmmax() - 1;
pb.flags = PBF_SIZE_PREFIX;
- ret = print_list_item(&d, &opts, &pb, current_time);
+ ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time);
if (ret < 0) {
free(status_items);
status_items = NULL;
return ret;
}
+ make_inode_status_items(&pb);
free(parser_friendly_status_items);
parser_friendly_status_items = pb.buf;
return 1;
}
/**
- * Mmap the given audio file and update statistics.
+ * Open the audio file with highest score and set up an afd structure.
*
- * \param aft_row Determines the audio file to be opened and updated.
- * \param score The score of the audio file.
* \param afd Result pointer.
*
* On success, the numplayed field of the audio file selector info is increased
*
* \return Positive shmid on success, negative on errors.
*/
-int open_and_update_audio_file(struct osl_row *aft_row, long score,
- struct audio_file_data *afd)
+int open_and_update_audio_file(struct audio_file_data *afd)
{
- unsigned char *aft_hash, file_hash[HASH_SIZE];
+ unsigned char file_hash[HASH_SIZE];
struct osl_object afsi_obj;
- struct afs_info old_afsi, new_afsi;
- int ret = get_hash_of_row(aft_row, &aft_hash);
+ struct afs_info new_afsi;
+ int ret;
struct afsi_change_event_data aced;
struct osl_object map, chunk_table_obj;
- char *path;
-
+ struct ls_data *d = &status_item_ls_data;
+again:
+ ret = score_get_best(¤t_aft_row, &d->score);
if (ret < 0)
return ret;
- ret = get_audio_file_path_of_row(aft_row, &path);
+ ret = get_hash_of_row(current_aft_row, &d->hash);
if (ret < 0)
return ret;
- PARA_NOTICE_LOG("%s\n", path);
- ret = get_afsi_object_of_row(aft_row, &afsi_obj);
+ ret = get_audio_file_path_of_row(current_aft_row, &d->path);
if (ret < 0)
return ret;
- ret = load_afsi(&old_afsi, &afsi_obj);
+ PARA_NOTICE_LOG("%s\n", d->path);
+ ret = get_afsi_object_of_row(current_aft_row, &afsi_obj);
if (ret < 0)
return ret;
- ret = get_afhi_of_row(aft_row, &afd->afhi);
+ ret = load_afsi(&d->afsi, &afsi_obj);
if (ret < 0)
return ret;
- afd->afhi.chunk_table = NULL;
- ret = osl(osl_open_disk_object(audio_file_table, aft_row,
+ ret = get_afhi_of_row(current_aft_row, &afd->afhi);
+ if (ret < 0)
+ return ret;
+ d->afhi = afd->afhi;
+ d->afhi.chunk_table = afd->afhi.chunk_table = NULL;
+ ret = osl(osl_open_disk_object(audio_file_table, current_aft_row,
AFTCOL_CHUNKS, &chunk_table_obj));
if (ret < 0)
return ret;
- ret = mmap_full_file(path, O_RDONLY, &map.data, &map.size, &afd->fd);
+ ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd);
if (ret < 0)
goto out;
hash_function(map.data, map.size, file_hash);
- ret = hash_compare(file_hash, aft_hash);
+ ret = hash_compare(file_hash, d->hash);
para_munmap(map.data, map.size);
if (ret) {
ret = -E_HASH_MISMATCH;
goto out;
}
- new_afsi = old_afsi;
+ new_afsi = d->afsi;
new_afsi.num_played++;
new_afsi.last_played = time(NULL);
save_afsi(&new_afsi, &afsi_obj); /* in-place update */
- afd->audio_format_id = old_afsi.audio_format_id;
+ afd->audio_format_id = d->afsi.audio_format_id;
load_chunk_table(&afd->afhi, chunk_table_obj.data);
- ret = make_status_items(afd, &old_afsi, path, score, file_hash);
- if (ret < 0)
- goto out;
- aced.aft_row = aft_row;
- aced.old_afsi = &old_afsi;
+ aced.aft_row = current_aft_row;
+ aced.old_afsi = &d->afsi;
+ /*
+ * No need to update the status items as the AFSI_CHANGE event will
+ * recreate them.
+ */
afs_event(AFSI_CHANGE, NULL, &aced);
ret = save_afd(afd);
out:
free(afd->afhi.chunk_table);
osl_close_disk_object(&chunk_table_obj);
- if (ret < 0)
- PARA_ERROR_LOG("%s: %s\n", path, para_strerror(-ret));
+ if (ret < 0) {
+ PARA_ERROR_LOG("%s: %s\n", d->path, para_strerror(-ret));
+ ret = score_delete(current_aft_row);
+ if (ret >= 0)
+ goto again;
+ }
return ret;
}
{
int i, ret;
struct private_add_data pad = {.cc = cc, .flags = 0};
- struct stat statbuf;
for (i = 1; i < cc->argc; i++) {
const char *arg = cc->argv[i];
return ret;
continue;
}
- ret = stat(path, &statbuf);
- if (ret < 0) {
- ret = send_sb_va(&cc->scc, SBD_ERROR_LOG,
- "failed to stat %s (%s)\n", path,
- strerror(errno));
- free(path);
- if (ret < 0)
- return ret;
- continue;
- }
- if (S_ISDIR(statbuf.st_mode))
+ if (ret == 1) /* directory */
ret = for_each_file_in_dir(path, add_one_audio_file,
&pad);
- else
+ else /* regular file */
ret = add_one_audio_file(path, &pad);
if (ret < 0) {
send_sb_va(&cc->scc, SBD_OUTPUT, "%s: %s\n", path,
struct touch_action_data tad = {.cto = query->data,
.pb = {
.max_size = shm_get_shmmax(),
- .private_data = &fd,
+ .private_data = &(struct afs_max_size_handler_data) {
+ .fd = fd,
+ .band = SBD_OUTPUT
+ },
.max_size_handler = afs_max_size_handler
}
};
struct com_rm_action_data crd = {.flags = *(uint32_t *)query->data,
.pb = {
.max_size = shm_get_shmmax(),
- .private_data = &fd,
+ .private_data = &(struct afs_max_size_handler_data) {
+ .fd = fd,
+ .band = SBD_OUTPUT
+ },
.max_size_handler = afs_max_size_handler
}
};
.flags = *(unsigned *)query->data,
.pb = {
.max_size = shm_get_shmmax(),
- .private_data = &fd,
+ .private_data = &(struct afs_max_size_handler_data) {
+ .fd = fd,
+ .band = SBD_OUTPUT
+ },
.max_size_handler = afs_max_size_handler
}
};
if (ret < 0)
return ret;
return audio_file_loop(data, clear_attribute);
- }
- default:
+ } case AFSI_CHANGE: {
+ struct afsi_change_event_data *aced = data;
+ uint64_t old_last_played = status_item_ls_data.afsi.last_played;
+ if (aced->aft_row != current_aft_row)
+ return 0;
+ ret = get_afsi_of_row(aced->aft_row, &status_item_ls_data.afsi);
+ if (ret < 0)
+ return ret;
+ status_item_ls_data.afsi.last_played = old_last_played;
+ make_status_items();
return 1;
+ } case AFHI_CHANGE: {
+ if (data != current_aft_row)
+ return 0;
+ ret = get_afhi_of_row(data, &status_item_ls_data.afhi);
+ if (ret < 0)
+ return ret;
+ make_status_items();
+ return 1;
+ } default:
+ return 0;
}
}
#include <sys/un.h>
#include <netdb.h>
#include <signal.h>
+#include <pwd.h>
#include "para.h"
#include "error.h"
static struct status_task status_task_struct;
+static uid_t *uid_whitelist;
+
/**
* the task that calls the status command of para_server
*
static void parse_config_or_die(void)
{
- int ret;
+ int ret, i;
char *config_file;
struct audiod_cmdline_parser_params params = {
.override = 0,
ret = file_exists(config_file);
if (conf.config_file_given && !ret) {
PARA_EMERG_LOG("can not read config file %s\n", config_file);
+ free(config_file);
goto err;
}
if (ret) {
daemon_set_loglevel(conf.loglevel_arg);
}
free(config_file);
+ if (conf.user_allow_given > 0) {
+ uid_whitelist = para_malloc(conf.user_allow_given
+ * sizeof(uid_t));
+ for (i = 0; i < conf.user_allow_given; i++) {
+ int32_t val;
+ struct passwd *pw;
+ ret = para_atoi32(conf.user_allow_arg[i], &val);
+ if (ret >= 0) {
+ uid_whitelist[i] = val;
+ continue;
+ }
+ errno = 0; /* see getpwnam(3) */
+ pw = getpwnam(conf.user_allow_arg[i]);
+ if (!pw) {
+ PARA_EMERG_LOG("invalid username: %s\n",
+ conf.user_allow_arg[i]);
+ goto err;
+ }
+ uid_whitelist[i] = pw->pw_uid;
+ }
+ }
return;
err:
- free(config_file);
exit(EXIT_FAILURE);
}
for (i = 0; i < 2; i++) {
if (ct->fd[i] < 0)
continue;
- ret = handle_connect(ct->fd[i], &s->rfds);
+ ret = handle_connect(ct->fd[i], &s->rfds, uid_whitelist);
if (ret < 0) {
PARA_ERROR_LOG("%s\n", para_strerror(-ret));
if (ret == -E_AUDIOD_TERM) {
close_unused_slots();
audiod_cmdline_parser_free(&conf);
close_stat_clients();
+ free(uid_whitelist);
}
/*
extern struct audiod_args_info conf;
extern int audiod_status;
-int handle_connect(int accept_fd, fd_set *rfds);
+int handle_connect(int accept_fd, fd_set *rfds, uid_t *uid_whitelist);
void audiod_status_dump(bool force);
char *get_time_string(void);
struct btr_node *audiod_get_btr_root(void);
return ret;
}
-static int check_perms(uid_t uid)
+static int check_perms(uid_t uid, uid_t *whitelist)
{
int i;
if (!conf.user_allow_given)
return 1;
for (i = 0; i < conf.user_allow_given; i++)
- if (uid == conf.user_allow_arg[i])
+ if (uid == whitelist[i])
return 1;
return -E_UCRED_PERM;
}
*
* \param accept_fd The fd to accept connections on.
* \param rfds If \a accept_fd is not set in \a rfds, do nothing.
+ * \param uid_whitelist Array of UIDs which are allowed to connect.
*
* This is called in each iteration of the select loop. If there is an incoming
* connection on \a accept_fd, this function reads the command sent by the peer,
*
* \sa para_accept(), recv_cred_buffer()
* */
-int handle_connect(int accept_fd, fd_set *rfds)
+int handle_connect(int accept_fd, fd_set *rfds, uid_t *uid_whitelist)
{
int i, argc, ret, clifd;
char buf[MAXLINE], **argv = NULL;
goto out;
uid = ret;
PARA_INFO_LOG("connection from user %i, buf: %s\n", ret, buf);
- ret = check_perms(uid);
+ ret = check_perms(uid, uid_whitelist);
if (ret < 0)
goto out;
ret = create_argv(buf, "\n", &argv);
static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly,
char **result)
{
- char mtime[30] = "";
char *status, *flags; /* vss status info */
/* nobody updates our version of "now" */
long offset = (nmmd->offset + 500) / 1000;
struct timeval current_time;
- struct tm mtime_tm;
struct para_buffer b = {.flags = parser_friendly? PBF_SIZE_PREFIX : 0};
/* report real status */
status = vss_status_tohuman(nmmd->vss_status_flags);
flags = vss_get_status_flags(nmmd->vss_status_flags);
- if (nmmd->size) { /* parent currently has an audio file open */
- localtime_r(&nmmd->mtime, &mtime_tm);
- strftime(mtime, 29, "%b %d %Y", &mtime_tm);
- }
clock_get_realtime(¤t_time);
/*
* The calls to WRITE_STATUS_ITEM() below never fail because
* is not smart enough to prove this and complains nevertheless.
* Casting the return value to void silences clang.
*/
- (void)WRITE_STATUS_ITEM(&b, SI_FILE_SIZE, "%zu\n", nmmd->size / 1024);
- (void)WRITE_STATUS_ITEM(&b, SI_MTIME, "%s\n", mtime);
(void)WRITE_STATUS_ITEM(&b, SI_STATUS, "%s\n", status);
(void)WRITE_STATUS_ITEM(&b, SI_STATUS_FLAGS, "%s\n", flags);
(void)WRITE_STATUS_ITEM(&b, SI_OFFSET, "%li\n", offset);
goto err;
if (chdir("/") < 0)
goto err;
- null = open("/dev/null", O_RDONLY);
+ null = open("/dev/null", O_RDWR);
if (null < 0)
goto err;
if (dup2(null, STDIN_FILENO) < 0)
#define AFT_ERRORS \
- PARA_ERROR(BAD_AFSI, "invaiid afs info"), \
+ PARA_ERROR(BAD_AFSI, "invalid afs info"), \
PARA_ERROR(LOCALTIME, "localtime() failed"), \
PARA_ERROR(STRFTIME, "strftime() failed"), \
PARA_ERROR(BAD_PATH, "invalid path"), \
option "user-allow" -
#~~~~~~~~~~~~~~~~~~~~
-"allow this uid"
-int typestr="uid"
-default="-1"
+"allow this user to connect to audiod"
+string typestr = "username"
optional
multiple
-details="
- Allow the user identified by \"uid\" to connect to para_audiod.
- May be specified multiple times. If not specified at all,
- all users are allowed to connect.
-
- This feature requires unix socket credentials and is currently
- only supported on Linux systems. On other operating systems,
- the option is silently ignored and all local users are allowed
- to connect to para_audiod.
+details = "
+ Allow the user identified by username (either a string or
+ a UID) to connect to para_audiod. This option may be given
+ multiple times. If not specified at all, all users are allowed
+ to connect.
+
+ This feature is based on the ability to send unix
+ credentials through local sockets using ancillary data
+ (SCM_CREDENTIALS). Currently it only works on Linux. On
+ other operating systems the option is silently ignored and
+ all local users are allowed to connect.
"
option "clock-diff-count" -
#~~~~~~~~~~~~~~~~
"run as background daemon"
flag off
-dependon="logfile"
-details="
- Note that </qu>CURRENT_PROGRAM<qu> refuses to start in daemon mode if no
- logfile was specified.
+details = "
+ If this option is given and no logfile was specified, all
+ messages go to /dev/null.
"
</qu>
struct misc_meta_data {
/** The size of the current audio file in bytes. */
size_t size;
- /** The last modification time of the current audio file. */
- time_t mtime;
/** The "old" status flags -- commands may only read them. */
unsigned int vss_status_flags;
/** The new status flags -- commands may set them. */
serverlog=server.log
get_audio_file_paths ogg
-oggs="$result"
+declare -a oggs=($result)
+declare -a oggs_base=(${oggs[@]##*/})
declare -a commands=() cmdline=() required_objects=() good=() bad=()
i=0
let i++
commands[$i]="add_ogg"
required_objects[$i]='ogg_afh'
-cmdline[$i]="add $oggs"
+cmdline[$i]="add ${oggs[@]}"
bad[$i]='.'
let i++
commands[$i]="ls_ogg"
required_objects[$i]='ogg_afh'
-cmdline[$i]="ls -lv -p $oggs"
-good[$i]='^path:'
+cmdline[$i]="ls -lv ${oggs_base[@]}"
+good[$i]='^basename:'
let i++
commands[$i]="term"
static char buf[512];
snprintf(buf, sizeof(buf) - 1, "%s\n"
- "Copyright (C) 2014 Andre Noll\n"
+ "Copyright (C) 2002-2015 Andre Noll\n"
"This is free software with ABSOLUTELY NO WARRANTY."
" See COPYING for details.\n"
"Report bugs to <maan@tuebingen.mpg.de>.\n"
mmd->afd.afhi.chunk_tv.tv_usec = 0;
free(mmd->afd.afhi.chunk_table);
mmd->afd.afhi.chunk_table = NULL;
- mmd->mtime = 0;
mmd->size = 0;
mmd->events++;
}
goto err;
}
mmd->size = statbuf.st_size;
- mmd->mtime = statbuf.st_mtime;
ret = para_mmap(mmd->size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
passed_fd, 0, &vsst->map);
if (ret < 0)
from the client to the server.
There is no knowledge about the server commands built into para_client,
-so it does not know about addblob commands. Instead, it inspects the
-first data package sent by the server for a magic string. If this
-string was found, it sends STDIN to the server, otherwise it dumps
-data from the server to STDOUT.
+so it does not know about addblob commands. Instead, the server sends
+a special "awaiting data" packet for these commands. If the client
+receives this packet, it sends STDIN to the server, otherwise it
+dumps data from the server to STDOUT.
Streaming protocols
~~~~~~~~~~~~~~~~~~~
operates on undecoded PCM data (visualizers, equalizers etc.). Such
filters are called _decoders_ in general, and xxxdec is the name of
the paraslash decoder for the audio format xxx. For example, the mp3
-decoder filter is called mp3dec.
+decoder is called mp3dec.
Note that the output of the decoder is about 10 times larger than
its input. This means that filters that operate on the decoded audio
Paraslash relies on external libraries for most decoders, so these
libraries must be installed for the decoder to be included in the
-para_filter and para_audiod executables. The oggdec filter depends
-on the libogg and libvorbis libraries for example.
+executables. For example, the mp3dec filter depends on the mad library.
Forward error correction
~~~~~~~~~~~~~~~~~~~~~~~~