Merge branch 't/makefile_cleanups'
authorAndre Noll <maan@systemlinux.org>
Wed, 16 Nov 2011 10:28:33 +0000 (11:28 +0100)
committerAndre Noll <maan@systemlinux.org>
Wed, 16 Nov 2011 10:29:25 +0000 (11:29 +0100)
16 files changed:
NEWS
alsa_write.c
audiod.c
daemon.c
daemon.h
error.h
net.h
server.c
signal.c
signal.h
t/makefile.test
t/t0001-oggdec-correctness.sh
t/t0004-server.sh [new file with mode: 0755]
t/test-lib.sh
vss.c
web/download.in.html

diff --git a/NEWS b/NEWS
index 6db7225..20ae784 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,13 @@
        - Shared memory areas are no longer restricted to 64K. We now
          detect the maximal size of a shared memory area at runtime.
        - cleanup of the internal uptime API.
+       - para_server prefaults the mmapped audio file to avoid
+         delays on slow media.
+       - A new test for the test-suite that exercises the
+         communication between para_server and para_audiod.
+       - The alsa writer eats up less CPU cycles when configured to
+         use the DMIX plugin.
+       - Makefile cleanups.
 
 --------------------------------------
 0.4.8 (2011-08-19) "nested assignment"
index 047e88f..e741578 100644 (file)
@@ -36,10 +36,6 @@ struct private_alsa_write_data {
        snd_pcm_t *handle;
        /** Determined and set by alsa_init(). */
        int bytes_per_frame;
-       /** The approximate maximum buffer duration in us. */
-       unsigned buffer_time;
-       /* Number of frames that fit into the buffer. */
-       snd_pcm_uframes_t buffer_frames;
        /**
         * The sample rate given by command line option or the decoder
         * of the writer node group.
@@ -76,71 +72,119 @@ static int alsa_init(struct private_alsa_write_data *pad,
        snd_pcm_hw_params_t *hwparams;
        snd_pcm_sw_params_t *swparams;
        snd_pcm_uframes_t start_threshold, stop_threshold;
-       snd_pcm_uframes_t period_size;
-       int err;
+       snd_pcm_uframes_t buffer_size, period_size;
+       snd_output_t *log;
+       unsigned buffer_time;
+       int ret;
+       const char *msg;
 
        PARA_INFO_LOG("opening %s\n", conf->device_arg);
-       err = snd_pcm_open(&pad->handle, conf->device_arg,
+       msg = "unable to open pcm";
+       ret = snd_pcm_open(&pad->handle, conf->device_arg,
                SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
-       if (err < 0)
-               return -E_PCM_OPEN;
-
+       if (ret < 0)
+               goto fail;
        snd_pcm_hw_params_alloca(&hwparams);
-       snd_pcm_sw_params_alloca(&swparams);
-       if (snd_pcm_hw_params_any(pad->handle, hwparams) < 0)
-               return -E_BROKEN_CONF;
-       if (snd_pcm_hw_params_set_access(pad->handle, hwparams,
-                       SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
-               return -E_ACCESS_TYPE;
-       if (snd_pcm_hw_params_set_format(pad->handle, hwparams,
-                       pad->sample_format) < 0)
-               return -E_SAMPLE_FORMAT;
-       if (snd_pcm_hw_params_set_channels(pad->handle, hwparams,
-                       pad->channels) < 0)
-               return -E_CHANNEL_COUNT;
-       if (snd_pcm_hw_params_set_rate_near(pad->handle, hwparams,
-                       &pad->sample_rate, NULL) < 0)
-               return -E_SET_RATE;
-       err = snd_pcm_hw_params_get_buffer_time_max(hwparams,
-               &pad->buffer_time, NULL);
-       if (err < 0 || !pad->buffer_time)
-               return -E_GET_BUFFER_TIME;
-       PARA_INFO_LOG("buffer time: %d\n", pad->buffer_time);
-       if (snd_pcm_hw_params_set_buffer_time_near(pad->handle, hwparams,
-                       &pad->buffer_time, NULL) < 0)
-               return -E_SET_BUFFER_TIME;
-       if (snd_pcm_hw_params(pad->handle, hwparams) < 0)
-               return -E_HW_PARAMS;
+       msg = "Broken alsa configuration";
+       ret = snd_pcm_hw_params_any(pad->handle, hwparams);
+       if (ret < 0)
+               goto fail;
+       msg = "access type not available";
+       ret = snd_pcm_hw_params_set_access(pad->handle, hwparams,
+               SND_PCM_ACCESS_RW_INTERLEAVED);
+       if (ret < 0)
+               goto fail;
+       msg = "sample format not available";
+       ret = snd_pcm_hw_params_set_format(pad->handle, hwparams,
+               pad->sample_format);
+       if (ret < 0)
+               goto fail;
+       msg = "channels count not available";
+       ret = snd_pcm_hw_params_set_channels(pad->handle, hwparams,
+               pad->channels);
+       if (ret < 0)
+               goto fail;
+       msg = "could not set sample rate";
+       ret = snd_pcm_hw_params_set_rate_near(pad->handle, hwparams,
+               &pad->sample_rate, NULL);
+       if (ret < 0)
+               goto fail;
+       msg = "unable to get buffer time";
+       ret = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time,
+               NULL);
+       if (ret < 0 || buffer_time == 0)
+               goto fail;
+       msg = "could not set buffer time";
+       ret = snd_pcm_hw_params_set_buffer_time_near(pad->handle, hwparams,
+               &buffer_time, NULL);
+       if (ret < 0)
+               goto fail;
+       msg = "unable to install hw params";
+       ret = snd_pcm_hw_params(pad->handle, hwparams);
+       if (ret < 0)
+               goto fail;
        snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL);
-       snd_pcm_hw_params_get_buffer_size(hwparams, &pad->buffer_frames);
-       PARA_INFO_LOG("buffer size: %lu, period_size: %lu\n", pad->buffer_frames,
-               period_size);
-       if (period_size == pad->buffer_frames)
-               return -E_BAD_PERIOD;
+       snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
+       msg = "period size equals buffer size";
+       if (period_size == buffer_size)
+               goto fail;
+
+       /* software parameter setup */
+       snd_pcm_sw_params_alloca(&swparams);
        snd_pcm_sw_params_current(pad->handle, swparams);
        snd_pcm_sw_params_set_avail_min(pad->handle, swparams, period_size);
-       if (pad->buffer_frames < 1)
+       if (buffer_size < 1)
                start_threshold = 1;
        else
-               start_threshold = PARA_MIN(pad->buffer_frames,
+               start_threshold = PARA_MIN(buffer_size,
                        (snd_pcm_uframes_t)pad->sample_rate);
-       if (snd_pcm_sw_params_set_start_threshold(pad->handle, swparams,
-                       start_threshold) < 0)
-               return -E_START_THRESHOLD;
-       stop_threshold = pad->buffer_frames;
-       if (snd_pcm_sw_params_set_stop_threshold(pad->handle, swparams,
-                       stop_threshold) < 0)
-               return -E_STOP_THRESHOLD;
-       if (snd_pcm_sw_params(pad->handle, swparams) < 0)
-               PARA_WARNING_LOG("unable to install sw params\n");
-       pad->bytes_per_frame = snd_pcm_format_physical_width(pad->sample_format)
-               * pad->channels / 8;
-       if (pad->bytes_per_frame <= 0)
-               return -E_PHYSICAL_WIDTH;
-       PARA_INFO_LOG("bytes per frame: %d\n", pad->bytes_per_frame);
-       if (snd_pcm_nonblock(pad->handle, 1))
-               PARA_ERROR_LOG("failed to set nonblock mode\n");
+       msg = "could not set start threshold";
+       ret = snd_pcm_sw_params_set_start_threshold(pad->handle, swparams,
+               start_threshold);
+       if (ret < 0)
+               goto fail;
+       stop_threshold = buffer_size;
+       msg = "could not set stop threshold";
+       ret = snd_pcm_sw_params_set_stop_threshold(pad->handle, swparams,
+               stop_threshold);
+       if (ret < 0)
+               goto fail;
+       msg = "unable to install sw params";
+       ret = snd_pcm_sw_params(pad->handle, swparams);
+       if (ret < 0)
+               goto fail;
+       msg = "unable to determine bytes per frame";
+       ret = snd_pcm_format_physical_width(pad->sample_format);
+       if (ret <= 0)
+               goto fail;
+       pad->bytes_per_frame = ret * pad->channels / 8;
+       msg = "failed to set alsa handle to nonblock mode";
+       ret = snd_pcm_nonblock(pad->handle, 1);
+       if (ret < 0)
+               goto fail;
+       ret = snd_output_buffer_open(&log);
+       if (ret == 0) {
+               char *buf;
+               PARA_INFO_LOG("dumping alsa configuration\n");
+               snd_pcm_dump(pad->handle, log);
+               snd_output_buffer_string(log, &buf);
+               for (;;) {
+                       char *p = strchr(buf, '\n');
+                       if (!p) /* omit last output line, it's empty */
+                               break;
+                       *p = '\0';
+                       PARA_INFO_LOG("%s\n", buf);
+                       buf = p + 1;
+               }
+               snd_output_close(log);
+       }
        return 1;
+fail:
+       if (ret < 0)
+               PARA_ERROR_LOG("%s: %s\n", msg, snd_strerror(-ret));
+       else
+               PARA_ERROR_LOG("%s\n", msg);
+       return -E_ALSA;
 }
 
 static void alsa_write_pre_select(struct sched *s, struct task *t)
@@ -164,8 +208,9 @@ static void alsa_write_pre_select(struct sched *s, struct task *t)
        }
        ret = snd_pcm_poll_descriptors(pad->handle, &pfd, 1);
        if (ret < 0) {
-               PARA_ERROR_LOG("%s\n", snd_strerror(-ret));
-               t->error = -E_ALSA_POLL_FDS;
+               PARA_ERROR_LOG("could not get alsa poll fd: %s\n",
+                       snd_strerror(-ret));
+               t->error = -E_ALSA;
                return;
        }
        pad->poll_fd = pfd.fd;
@@ -248,12 +293,23 @@ again:
                wn->min_iqs = pad->bytes_per_frame;
                goto again;
        }
-       if (pad->poll_fd >= 0 && !FD_ISSET(pad->poll_fd, &s->rfds))
+       if (pad->poll_fd < 0 || !FD_ISSET(pad->poll_fd, &s->rfds))
                return;
        frames = bytes / pad->bytes_per_frame;
        frames = snd_pcm_writei(pad->handle, data, frames);
-       if (frames == 0 || frames == -EAGAIN)
+       if (frames == 0 || frames == -EAGAIN) {
+               /*
+                * The alsa poll fd was ready for IO but we failed to write to
+                * the alsa handle. This means another event is pending. We
+                * don't care about that but we have to dispatch the event in
+                * order to avoid a busy loop. So we simply read from the poll
+                * fd.
+                */
+               char buf[100];
+               if (read(pad->poll_fd, buf, 100))
+                       do_nothing;
                return;
+       }
        if (frames > 0) {
                btr_consume(btrn, frames * pad->bytes_per_frame);
                goto again;
@@ -263,8 +319,8 @@ again:
                snd_pcm_prepare(pad->handle);
                return;
        }
-       PARA_WARNING_LOG("%s\n", snd_strerror(-frames));
-       ret = -E_ALSA_WRITE;
+       PARA_ERROR_LOG("alsa write error: %s\n", snd_strerror(-frames));
+       ret = -E_ALSA;
 err:
        assert(ret < 0);
        btr_remove_node(btrn);
index 4864c85..8008e2c 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -1381,7 +1381,7 @@ int main(int argc, char *argv[])
        init_command_task(cmd_task);
 
        if (conf.daemon_given)
-               daemonize();
+               daemonize(false /* parent exits immediately */);
 
        register_task(&sig_task->task);
        register_task(&cmd_task->task);
index ffdec4e..541e44d 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -142,14 +142,25 @@ static bool daemon_test_flag(unsigned flag)
        return me->flags & flag;
 }
 
+static void dummy_sighandler(__a_unused int s)
+{
+}
+
 /**
  * Do the usual stuff to become a daemon.
  *
- * Fork, become session leader, dup fd 0, 1, 2 to /dev/null.
+ * \param parent_waits Whether the parent process should pause before exit.
  *
- * \sa fork(2), setsid(2), dup(2).
+ * Fork, become session leader, cd to /, and dup fd 0, 1, 2 to /dev/null. If \a
+ * parent_waits is false, the parent process terminates immediately.
+ * Otherwise, it calls pause() to sleep until it receives \p SIGTERM or \p
+ * SIGCHLD and exits successfully thereafter. This behaviour is useful if the
+ * daemon process should not detach from the console until the child process
+ * has completed its setup.
+ *
+ * \sa fork(2), setsid(2), dup(2), pause(2).
  */
-void daemonize(void)
+void daemonize(bool parent_waits)
 {
        pid_t pid;
        int null;
@@ -158,8 +169,14 @@ void daemonize(void)
        pid = fork();
        if (pid < 0)
                goto err;
-       if (pid)
+       if (pid) {
+               if (parent_waits) {
+                       signal(SIGTERM, dummy_sighandler);
+                       signal(SIGCHLD, dummy_sighandler);
+                       pause();
+               }
                exit(EXIT_SUCCESS); /* parent exits */
+       }
        /* become session leader */
        if (setsid() < 0)
                goto err;
index d5583f5..3fe72ea 100644 (file)
--- a/daemon.h
+++ b/daemon.h
@@ -1,7 +1,7 @@
 /** \file daemon.h exported symbols from daemon.c */
 
 
-void daemonize(void);
+void daemonize(bool parent_waits);
 void daemon_open_log_or_die(void);
 void daemon_close_log(void);
 void log_welcome(const char *whoami);
diff --git a/error.h b/error.h
index 6549413..b3eb7e6 100644 (file)
--- a/error.h
+++ b/error.h
@@ -425,21 +425,7 @@ extern const char **para_errlist[];
 
 
 #define ALSA_WRITE_ERRORS \
-       PARA_ERROR(BROKEN_CONF, "Broken alsa configuration"), \
-       PARA_ERROR(ACCESS_TYPE, "alsa access type not available"), \
-       PARA_ERROR(SAMPLE_FORMAT, "sample format not available"), \
-       PARA_ERROR(CHANNEL_COUNT, "channels count not available"), \
-       PARA_ERROR(HW_PARAMS, "unable to install hw params"), \
-       PARA_ERROR(BAD_PERIOD, "can not use period equal to buffer size"), \
-       PARA_ERROR(ALSA_WRITE, "alsa write error"), \
-       PARA_ERROR(PCM_OPEN, "unable to open pcm"), \
-       PARA_ERROR(PHYSICAL_WIDTH, "unable to determine bytes per frame"), \
-       PARA_ERROR(GET_BUFFER_TIME, "snd_pcm_hw_params_get_buffer_time_max() failed"), \
-       PARA_ERROR(SET_BUFFER_TIME, "snd_pcm_hw_params_set_buffer_time_near() failed"), \
-       PARA_ERROR(SET_RATE, "snd_pcm_hw_params_set_rate_near failed"), \
-       PARA_ERROR(START_THRESHOLD, "snd_pcm_sw_params_set_start_threshold() failed"), \
-       PARA_ERROR(STOP_THRESHOLD, "snd_pcm_sw_params_set_stop_threshold() failed"), \
-       PARA_ERROR(ALSA_POLL_FDS, "could not get alsa poll fd"), \
+       PARA_ERROR(ALSA, "alsa error"), \
 
 
 #define WRITE_COMMON_ERRORS \
diff --git a/net.h b/net.h
index 80f5794..79c5994 100644 (file)
--- a/net.h
+++ b/net.h
@@ -8,13 +8,15 @@
 /** \file net.h exported symbols from net.c */
 
 /**
- * the buffer size of the sun_path component of struct sockaddr_un
+ * The buffer size of the sun_path component of struct sockaddr_un.
  *
- * While glibc doesn't define \p UNIX_PATH_MAX, it
- * documents it has being limited to 108 bytes.
+ * While glibc doesn't define \p UNIX_PATH_MAX, it documents it has being
+ * limited to 108 bytes. On NetBSD it is only 104 bytes though. We trust \p
+ * UNIX_PATH_MAX if it is defined and use the size of the ->sun_path member
+ * otherwise. This should be safe everywhere.
  */
 #ifndef UNIX_PATH_MAX
-#define UNIX_PATH_MAX 108
+#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)0)->sun_path))
 #endif
 
 /* Userland defines for Linux DCCP support. */
index 15ae5c9..7f020c8 100644 (file)
--- a/server.c
+++ b/server.c
@@ -434,7 +434,7 @@ err:
        exit(EXIT_FAILURE);
 }
 
-static int init_afs(void)
+static int init_afs(int argc, char **argv)
 {
        int ret, afs_server_socket[2];
        pid_t afs_pid;
@@ -448,6 +448,10 @@ static int init_afs(void)
        if (afs_pid < 0)
                exit(EXIT_FAILURE);
        if (afs_pid == 0) { /* child (afs) */
+               int i;
+               for (i = argc - 1; i >= 0; i--)
+                       memset(argv[i], 0, strlen(argv[i]));
+               sprintf(argv[0], "para_server (afs)");
                close(afs_server_socket[0]);
                afs_init(afs_socket_cookie, afs_server_socket[1]);
        }
@@ -462,12 +466,6 @@ static int init_afs(void)
        return afs_server_socket[0];
 }
 
-__noreturn static void tmp_sigchld_handler(__a_unused int s)
-{
-       PARA_EMERG_LOG("caught early SIGCHLD\n");
-       exit(EXIT_FAILURE);
-}
-
 static void server_init(int argc, char **argv)
 {
        struct server_cmdline_parser_params params = {
@@ -495,7 +493,7 @@ static void server_init(int argc, char **argv)
        init_user_list(user_list_file);
        /* become daemon */
        if (conf.daemon_given)
-               daemonize();
+               daemonize(true /* parent waits for SIGTERM */);
        PARA_NOTICE_LOG("initializing audio format handlers\n");
        afh_init();
 
@@ -508,17 +506,21 @@ static void server_init(int argc, char **argv)
         */
        para_sigaction(SIGUSR1, SIG_IGN);
        /*
-        * We have to install a SIGCHLD handler before the afs process is being
-        * forked off. Otherwise, para_server does not notice if afs dies before
-        * the SIGCHLD handler has been installed by init_signal_task() below.
+        * We have to block SIGCHLD before the afs process is being forked off.
+        * Otherwise, para_server does not notice if afs dies before the
+        * SIGCHLD handler has been installed for the parent process by
+        * init_signal_task() below.
         */
-       para_sigaction(SIGCHLD, tmp_sigchld_handler);
+       para_block_signal(SIGCHLD);
        PARA_NOTICE_LOG("initializing the audio file selector\n");
-       afs_socket = init_afs();
+       afs_socket = init_afs(argc, argv);
        init_signal_task();
+       para_unblock_signal(SIGCHLD);
        PARA_NOTICE_LOG("initializing virtual streaming system\n");
        init_vss_task(afs_socket);
        init_server_command_task(argc, argv);
+       if (conf.daemon_given)
+               kill(getppid(), SIGTERM);
        PARA_NOTICE_LOG("server init complete\n");
 }
 
index f02c453..aa63c8b 100644 (file)
--- a/signal.c
+++ b/signal.c
@@ -147,10 +147,50 @@ void para_install_sighandler(int sig)
        para_sigaction(sig, &generic_signal_handler);
 }
 
+/**
+ * Block a signal for the caller.
+ *
+ * \param sig The signal to block.
+ *
+ * This sets the given signal in the current signal mask of the calling process
+ * to prevent this signal from delivery.
+ *
+ * \sa \ref para_unblock_signal(), sigprocmask(2), sigaddset(3).
+ */
+void para_block_signal(int sig)
+{
+       sigset_t set;
+
+       PARA_DEBUG_LOG("blocking signal %d\n", sig);
+       sigemptyset(&set);
+       sigaddset(&set, sig);
+       sigprocmask(SIG_BLOCK, &set, NULL);
+}
+
+/**
+ * Unblock a signal.
+ *
+ * \param sig The signal to unblock.
+ *
+ * This function removes the given signal from the current set of blocked
+ * signals.
+ *
+ * \sa \ref para_block_signal(), sigprocmask(2), sigaddset(3).
+ */
+void para_unblock_signal(int sig)
+{
+       sigset_t set;
+
+       PARA_DEBUG_LOG("unblocking signal %d\n", sig);
+       sigemptyset(&set);
+       sigaddset(&set, sig);
+       sigprocmask(SIG_UNBLOCK, &set, NULL);
+}
+
 /**
  * Return the number of the next pending signal.
  *
- * \param rfds Th fd_set containing the signal pipe.
+ * \param rfds The fd_set containing the signal pipe.
  *
  * \return On success, the number of the received signal is returned. If there
  * is no signal currently pending, the function returns zero. On read errors
index 799c317..266b3ab 100644 (file)
--- a/signal.h
+++ b/signal.h
@@ -22,3 +22,5 @@ void para_install_sighandler(int);
 int para_reap_child(pid_t *pid);
 int para_next_signal(fd_set *rfds);
 void para_signal_shutdown(void);
+void para_block_signal(int sig);
+void para_unblock_signal(int sig);
index d4aedf8..4bbc5d7 100644 (file)
@@ -15,7 +15,7 @@ ifdef V
        endif
 endif
 
-tests := $(wildcard $(test_dir)/t[0-9][0-9][0-9][0-9]-*.sh)
+tests := $(sort $(wildcard $(test_dir)/t[0-9][0-9][0-9][0-9]-*.sh))
 
 test: $(tests)
 
index 01260e5..554dfdf 100755 (executable)
@@ -11,7 +11,7 @@ implementation.'
 test_require_objects "oggdec_filter"
 missing_objects="$result"
 
-test_require_executables "oggdec"
+test_require_executables oggdec sha1sum
 missing_executables="$result"
 
 get_audio_file_paths ogg
diff --git a/t/t0004-server.sh b/t/t0004-server.sh
new file mode 100755 (executable)
index 0000000..c79ea24
--- /dev/null
@@ -0,0 +1,125 @@
+#!/usr/bin/env bash
+
+test_description='Check if server command socket works.
+
+A new ssh key pair is generated, para_server is started and some commands are
+sent to the server by executing para_client. This is an implicit check of the
+crypto functions.
+'
+
+. ${0%/*}/test-lib.sh
+
+loglevel=debug
+port=2991
+stream_port=8001
+# need absolute paths here because server cds to / in daemon mode
+db=$(pwd)/db
+sock=$(pwd)/sock
+user_list=ul
+privkey=key
+pubkey=$privkey.pub
+serverlog=server.log
+
+get_audio_file_paths ogg
+oggs="$result"
+
+declare -a commands=() cmdline=() required_objects=() good=() bad=()
+i=0
+commands[$i]="help"
+cmdline[$i]="help"
+good[$i]='help server  ----'
+
+let i++
+commands[$i]="init"
+cmdline[$i]="init"
+good[$i]='^successfully'
+bad[$i]='!^successfully'
+
+let i++
+commands[$i]="add_ogg"
+required_objects[$i]='ogg_afh'
+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:'
+
+let i++
+commands[$i]="term"
+cmdline[$i]="term"
+bad[$i]='.'
+
+test_require_objects "server"
+missing_objects="$result"
+test_require_executables "ssh-keygen"
+missing_executables="$result"
+
+ssh-keygen -q -t rsa -b 2048 -N "" -f $privkey
+key_gen_result=$?
+
+read &>/dev/null < /dev/tcp/localhost/$port
+check_port_result=$?
+
+cat > $user_list << EOF
+user $LOGNAME $pubkey AFS_READ,AFS_WRITE,VSS_READ,VSS_WRITE
+EOF
+
+# para_server sends this signal to all processes in the current process group.
+trap "" SIGUSR1
+
+$PARA_SERVER \
+       --logfile "$serverlog" \
+       --config_file /dev/null \
+       --daemon \
+       --loglevel $loglevel \
+       --port $port \
+       --afs_database_dir "$db" \
+       --afs_socket "$sock" \
+       --user_list "$user_list" \
+       --http_port "$stream_port" \
+       --dccp_port "$stream_port"
+
+for ((i=0; i < ${#commands[@]}; i++)); do
+       command=${commands[$i]}
+       if [[ -n "$missing_objects" ]]; then
+               test_skip "$command" "missing object(s): $missing_objects"
+               continue
+       fi
+       if [[ -n "$missing_executables" ]]; then
+               test_skip "$command" \
+                       "missing executables(s): $missing_executables"
+               continue
+       fi
+       if (($key_gen_result != 0)); then
+               test_skip "$command" "ssh-keygen failed"
+               continue
+       fi
+       if (($check_port_result == 0)); then
+               test_skip "$command" "port $port already in use"
+               continue
+       fi
+       if [[ -n "${required_objects[$i]}" ]]; then
+               test_require_objects "${required_objects[$i]}"
+               if [[ -n "$result" ]]; then
+                       test_skip "$command" "requires object $result"
+                       continue
+               fi
+       fi
+       test_expect_success "$command" "
+       $PARA_CLIENT \
+               --loglevel $loglevel \
+               --server_port $port \
+               --key_file $privkey \
+               --config_file /dev/null \
+               -- \
+               ${cmdline[$i]} > $command.out &&
+               { [[ -z \"${good[$i]}\" ]] || grep \"${good[$i]}\"; } < $command.out &&
+               { [[ -z \"${bad[$i]}\" ]]  || ! grep \"${bad[$i]}\"; } < $command.out
+       "
+done
+
+trap SIGUSR1 # reset to the value it had upon entrance to the shell
+test_done
index b7d675e..f1bb8cf 100644 (file)
@@ -162,7 +162,7 @@ test_duration()
 test_expect_success()
 {
        (($# != 2)) && error "bug: not 2 parameters to test_expect_success()"
-       say >&3 "expecting success: $2"
+       echo >&3 "expecting success: $2"
        _test_run "$1" "$2" "success"
        echo >&3 ""
 }
@@ -170,7 +170,7 @@ test_expect_success()
 test_expect_failure()
 {
        (($# != 2)) && error "bug: not 2 parameters to test_expect_failure()"
-       say >&3 "expecting failure: $2"
+       echo >&3 "expecting failure: $2"
        _test_run "$1" "$2" "failure"
        echo >&3 ""
 }
diff --git a/vss.c b/vss.c
index f6da52d..4a8aafa 100644 (file)
--- a/vss.c
+++ b/vss.c
@@ -361,8 +361,17 @@ static void vss_get_chunk(int chunk_num, struct vss_task *vsst,
 static void compute_group_size(struct vss_task *vsst, struct fec_group *g,
                int max_bytes)
 {
+       char *buf;
+       size_t len;
        int i, max_chunks = PARA_MAX(1LU, 150 / tv2ms(vss_chunk_time()));
 
+       if (g->first_chunk == 0) {
+               g->num_chunks = 1;
+               vss_get_chunk(0, vsst, &buf, &len);
+               g->bytes = len;
+               return;
+       }
+
        g->num_chunks = 0;
        g->bytes = 0;
        /*
@@ -372,8 +381,6 @@ static void compute_group_size(struct vss_task *vsst, struct fec_group *g,
         * of exactly one chunk for these audio formats.
         */
        for (i = 0;; i++) {
-               char *buf;
-               size_t len;
                int chunk_num = g->first_chunk + i;
 
                if (g->bytes > 0 && i >= max_chunks) /* duration limit */
@@ -502,7 +509,7 @@ static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
 {
        int ret, i, k, n, data_slices;
        size_t len;
-       char *buf;
+       char *buf, *p;
        struct fec_group *g = &fc->group;
 
        if (fc->state == FEC_STATE_NONE) {
@@ -562,16 +569,20 @@ static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
                assert(i == g->num_header_slices - 1);
        }
 
-       /* setup data slices */
+       /*
+        * Setup data slices. Note that for ogg streams chunk 0 points to a
+        * buffer on the heap rather than to the mapped audio file.
+        */
        vss_get_chunk(g->first_chunk, vsst, &buf, &len);
-       for (; i < g->num_header_slices + data_slices; i++) {
-               if (buf + g->slice_bytes > vsst->map + mmd->size) {
+       for (p = buf; i < g->num_header_slices + data_slices; i++) {
+               if (p + g->slice_bytes > buf + g->bytes) {
                        /*
-                        * Can not use the memory mapped audio file for this
-                        * slice as it goes beyond the map.
+                        * We must make a copy for this slice since using p
+                        * directly would exceed the buffer.
                         */
-                       uint32_t payload_size = vsst->map + mmd->size - buf;
-                       memcpy(fc->extra_src_buf, buf, payload_size);
+                       uint32_t payload_size = buf + g->bytes - p;
+                       assert(payload_size + FEC_HEADER_SIZE <= fc->mps);
+                       memcpy(fc->extra_src_buf, p, payload_size);
                        if (payload_size < g->slice_bytes)
                                memset(fc->extra_src_buf + payload_size, 0,
                                        g->slice_bytes - payload_size);
@@ -579,8 +590,8 @@ static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
                        i++;
                        break;
                }
-               fc->src_data[i] = (const unsigned char *)buf;
-               buf += g->slice_bytes;
+               fc->src_data[i] = (const unsigned char *)p;
+               p += g->slice_bytes;
        }
        if (i < k) {
                /* use arbitrary data for all remaining slices */
@@ -953,6 +964,10 @@ static int recv_afs_msg(int afs_socket, int *fd, uint32_t *code, uint32_t *data)
        return 1;
 }
 
+#ifndef MAP_POPULATE
+#define MAP_POPULATE 0
+#endif
+
 static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
 {
        int ret, passed_fd, shmid;
@@ -987,8 +1002,8 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
        }
        mmd->size = statbuf.st_size;
        mmd->mtime = statbuf.st_mtime;
-       ret = para_mmap(mmd->size, PROT_READ, MAP_PRIVATE, passed_fd,
-               0, &vsst->map);
+       ret = para_mmap(mmd->size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
+               passed_fd, 0, &vsst->map);
        if (ret < 0)
                goto err;
        close(passed_fd);
@@ -1075,6 +1090,25 @@ static void vss_send(struct vss_task *vsst)
                        senders[i].send(mmd->current_chunk, mmd->chunks_sent,
                                buf, len, vsst->header_buf, vsst->header_len);
                }
+               /*
+                * Prefault next chunk(s)
+                *
+                * If the backing device of the memory-mapped audio file is
+                * slow and read-ahead is turned off or prevented for some
+                * reason, e.g. due to memory pressure, it may take much longer
+                * than the chunk interval to get the next chunk on the wire,
+                * causing buffer underruns on the client side. Mapping the
+                * file with MAP_POPULATE seems to help a bit, but it does not
+                * eliminate the delays completely. Moreover, it is supported
+                * only on Linux. So we do our own read-ahead here.
+                */
+               if (mmd->current_chunk > 0) { /* chunk 0 might be on the heap */
+                       buf += len;
+                       for (i = 0; i < 5 && buf < vsst->map + mmd->size; i++) {
+                               __a_unused volatile char x = *buf;
+                               buf += 4096;
+                       }
+               }
                mmd->chunks_sent++;
                mmd->current_chunk++;
        }
index d735d5e..1da6c36 100644 (file)
@@ -1,25 +1,13 @@
 <h1>Download</h1>
 <hr>
 
-<p> Download the latest version from the
+<p> Clone the git repository by executing </p>
 
-<a href="versions/">download directory</a>
+<p> <b> git clone git://paraslash.systemlinux.org/git paraslash </b> </p>
 
-or grab a
-
-<a href="versions/paraslash-git.tar.bz2">tarball</a>
-
-of the current master branch. This version is expected to be more
-stable than any of the released versions.
-
-All regular releases are <a href="PUBLIC_KEY">cryptographically signed</a>.
-
-Anonymous (read-only)
-
-<a href="http://www.kernel.org/pub/software/scm/git/docs/">git</a>
-
-access is also available. Check out a copy with </p>
-
-<p>
-git clone git://paraslash.systemlinux.org/git paraslash
-</p>
+<p> Or grab the <a href="versions/paraslash-git.tar.bz2">tarball</a>
+of the current master branch, or download the latest version from the
+<a href="versions/">download directory</a>. All regular releases are
+<a href="PUBLIC_KEY">cryptographically signed</a>. Since development
+takes place in separate topic branches the master branch is expected
+to be more stable than any of the released versions. </p>