/* $Id: rplayd.c,v 1.8 1999/03/10 07:58:04 boyns Exp $ */
/*
* Copyright (C) 1993-99 Mark R. Boyns <boyns@doit.org>
*
* This file is part of rplay.
*
* rplay is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* rplay is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with rplay; see the file COPYING. If not, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "version.h"
#include "rplay.h"
#include <sys/param.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <sys/file.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif /* HAVE_SYS_WAIT_H */
#ifdef ultrix
#include <fcntl.h>
#else
#include <sys/fcntl.h>
#endif
#include <sys/uio.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include <sys/errno.h>
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <pwd.h>
#include <grp.h>
#include "rplayd.h"
#include "connection.h"
#include "spool.h"
#include "sound.h"
#ifdef AUTH
#include "host.h"
#endif /* AUTH */
#include "server.h"
#include "misc.h"
#include "cache.h"
#include "timer.h"
#include "strdup.h"
#include "getopt.h"
#include "tilde.h"
#ifdef HAVE_CDROM
#include "cdrom.h"
#endif /* HAVE_CDROM */
#ifdef HAVE_HELPERS
#include "helper.h"
#endif /* HAVE_HELPERS */
/* Make sure MAXHOSTNAMELEN is defined. */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 256
#endif
/* Global variables */
fd_set read_mask; /* Mask of RPTP clients that are reading. */
fd_set write_mask; /* Mask of RPTP clients that are writing. */
int debug = 0; /* Is debugging enable? (--debug) */
int rptp_timeout = RPTP_CONNECTION_TIMEOUT; /* --connection-timeout */
char hostname[MAXHOSTNAMELEN]; /* Name of the local system. */
char *hostaddr; /* IP address of the local system. */
int rplay_fd = -1; /* Socket used for RPLAY packets. */
int rptp_fd = -1; /* Socket used for RPTP connections. */
#ifdef OTHER_RPLAY_PORTS
int other_rplay_fd = -1; /* Another RPLAY socket. */
int other_rptp_fd = -1; /* Another RPTP socket. */
#endif
int curr_rate; /* Current audio rate. */
#ifndef HAVE_OSS
int curr_bufsize; /* Current audio bufsize. */
#endif /* !HAVE_OSS */
int rplayd_pid; /* Process ID of rplayd. */
time_t starttime; /* The time in seconds that rplayd started. */
#ifdef AUTH
int auth_enabled = 1; /* Host authentication. (--auth, --no-auth) */
#endif /* AUTH */
int rplay_priority_threshold = RPLAY_DEFAULT_PRIORITY; /* Default priority level allowed. */
/* Global audio-specific variables */
char *rplay_audio_device = RPLAY_AUDIO_DEVICE; /* Which audio device to use. */
#ifndef HAVE_OSS
int rplay_audio_bufsize = 0; /* --audio-bufsize */
#endif /* !HAVE_OSS */
int rplay_audio_rate = 0; /* --audio-rate */
int rplay_audio_sample_rate = 0; /* --audio-sample-rate */
int rplay_audio_channels = 0; /* --audio-channels */
int rplay_audio_format = 0; /* --audio-format */
int rplay_audio_precision = 0; /* --audio-precison */
int rplay_audio_port = 0; /* --audio_port */
int audio_enabled = 1; /* --no-audio */
int rplay_audio_volume = RPLAY_DEFAULT_VOLUME; /* Current audio volume. */
int rplay_audio_match = 0;
RPLAY_AUDIO_TABLE *rplay_audio_table = NULL;
/* Optional audio parameters specified on the command line. */
int optional_sample_rate = 0;
int optional_precision = 0;
int optional_channels = 0;
int optional_format = 0;
int optional_port = 0;
/* Default audio parameters. */
int default_sample_rate = 0;
int default_precision = 0;
int default_channels = 0;
int default_format = 0;
int default_port = 0;
#ifdef HAVE_OSS
int rplay_audio_fragsize = 0;
int optional_fragsize = 0;
int default_fragsize = 0;
#endif /* HAVE_OSS */
/* The audio buffer and its current and maximum size. */
int rplay_audio_size = 0;
char *rplay_audio_buf = NULL;
int max_rplay_audio_bufsize = 0;
/* Audio levels. */
int rplay_audio_left_level = 0;
int rplay_audio_right_level = 0;
static int monitor_total_bytes = 176400 * 5; /* 5 seconds */
static int monitor_update = 0;
int monitor_count = 0;
BUFFER *monitor_buffers = NULL;
int rplay_audio_timeout = RPLAY_AUDIO_TIMEOUT;
static int rplay_audio_flush_timeout = RPLAY_AUDIO_FLUSH_TIMEOUT;
static int rplayd_timeout = RPLAYD_TIMEOUT; /* seconds */
static int sound_cleanup_timeout = 10; /* seconds */
static int buffer_cleanup_timeout = 10; /* seconds */
static int spool_cleanup_timeout = 0; /* seconds -- disabled for now */
static int need_to_reset = 0;
static char *progname;
static char *sounds_file = NULL;
#ifdef AUTH
static char *hosts_file = NULL;
#endif /* AUTH */
static char *servers_file = NULL;
static char *cache_dir = NULL;
static char *log_file = NULL;
#ifdef HAVE_HELPERS
static char *helpers_file = NULL;
#endif /* HAVE_HELPERS */
static int logging = 0;
static int report_level;
static int log_fd = -1;
static int forward_fd = -1;
static char *forward = NULL;
static int rplay_port = 0;
static int rptp_port = 0;
#ifdef OTHER_RPLAY_PORTS
static int other_rplay_port = 0;
static int other_rptp_port = 0;
#endif /* OTHER_RPLAY_PORTS */
static int audio_test_mode = 0; /* enable audio test mode */
static int do_fork = 1;
static int inetd = 0; /* Was rplayd started by inetd? */
#ifndef RPLAYD_USER
#define RPLAYD_USER ""
#endif
#ifndef RPLAYD_GROUP
#define RPLAYD_GROUP ""
#endif
static char *run_as_user = RPLAYD_USER;
static char *run_as_group = RPLAYD_GROUP;
extern char *optarg; /* getopt_long */
extern int optind; /* getopt_long */
/*
* Long options for getopt_long.
*/
static struct option longopts[] =
{
{"audio-device", required_argument, NULL, 'A'},
#ifndef HAVE_OSS
{"audio-bufsize", required_argument, NULL, 'b'},
#endif /* !HAVE_OSS */
{"audio-bits", required_argument, NULL, 'B'},
{"audio-channels", required_argument, NULL, 1},
{"audio-close", required_argument, NULL, 'c'},
{"audio-flush", required_argument, NULL, 'F'},
{"audio-format", required_argument, NULL, 3},
#ifdef HAVE_OSS
{"audio-fragsize", required_argument, NULL, 18},
#endif /* HAVE_OSS */
{"audio-info", required_argument, NULL, 'i'},
{"audio-info-ulaw", no_argument, NULL, 13},
{"audio-match", no_argument, &rplay_audio_match, 1},
{"audio-port", required_argument, NULL, 10},
{"audio-rate", required_argument, NULL, 'r'},
{"audio-sample-rate", required_argument, NULL, 'R'},
{"audio-test", no_argument, &audio_test_mode, 1},
#ifdef AUTH
{"auth", no_argument, &auth_enabled, 1},
#endif
{"cache-directory", required_argument, NULL, 'D'},
{"cache-remove", no_argument, NULL, 12},
{"cache-size", required_argument, NULL, 's'},
#ifdef HAVE_CDROM
{"cdrom0", required_argument, NULL, 14},
{"cdrom1", required_argument, NULL, 15},
{"cdrom2", required_argument, NULL, 16},
{"cdrom3", required_argument, NULL, 17},
#endif /* HAVE_CDROM */
{"conf", required_argument, NULL, 'C'},
{"connection-timeout", required_argument, NULL, 'T'},
{"debug", no_argument, NULL, 'd'},
{"fork", no_argument, &do_fork, 1},
{"forward", required_argument, NULL, 'f'},
{"group", required_argument, NULL, 21},
{"help", no_argument, NULL, 2},
#ifdef HAVE_HELPERS
{"helpers", required_argument, NULL, 20},
#endif /* HAVE_HELPERS */
#ifdef AUTH
{"hosts", required_argument, NULL, 'H'},
#endif
{"inetd", no_argument, &inetd, 1},
{"info", required_argument, NULL, 'i'},
{"info-ulaw", no_argument, NULL, 13},
{"log-file", required_argument, NULL, 'L'},
{"log-level", required_argument, NULL, 'l'},
{"memory-cache-size", required_argument, NULL, 4},
{"memory-cache-sound-size", required_argument, NULL, 5},
{"no-audio", no_argument, NULL, 'N'},
#ifdef AUTH
{"no-auth", no_argument, &auth_enabled, 0},
#endif
{"no-fork", no_argument, &do_fork, 0},
{"no-inetd", no_argument, NULL, 'n'},
{"option-file", required_argument, NULL, 11},
{"options-file", required_argument, NULL, 11},
#ifdef OTHER_RPLAY_PORTS
{"other-rplay-port", required_argument, NULL, 9},
{"other-rptp-port", required_argument, NULL, 8},
#endif
{"port", required_argument, NULL, 6},
{"rplay-port", required_argument, NULL, 6},
{"rptp-port", required_argument, NULL, 7},
{"servers", required_argument, NULL, 'S'},
{"timeout", required_argument, NULL, 't'},
{"user", required_argument, NULL, 22},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0}
};
#ifdef sun
#ifdef __STDC__
extern int gethostname(char *name, int namelen);
#else
extern int gethostname( /* char *name, int namelen */ );
#endif
#endif
extern char *ctime();
#ifdef __STDC__
static void do_option_file(char *option_file);
static void do_option(int option_value);
#else
static void do_option_file( /* char *option_file */ );
static void do_option( /* int option_value */ );
#endif
static void doit();
static void handle_signals();
static void handle_sighup();
static void handle_sigint();
static void handle_sigchld();
static void reset();
static void audio_test();
#if defined(HAVE_CDROM) || defined(HAVE_HELPERS)
#ifdef __STDC__
void rplayd_pipe_read( /* fd_set *rfds */ );
#else
void rplayd_pipe_read( /* fd_set *rfds */ );
#endif
#endif
#ifdef AUTH
#define OPTIONS "A:C:D:F:S:b:B:c:df:l:L:Nnr:R:s:t:T:vi:H:"
#else
#define OPTIONS "A:C:D:F:S:b:B:c:df:l:L:Nnr:R:s:t:T:vi:"
#endif
#ifdef __STDC__
main(int argc, char **argv)
#else
main(argc, argv)
int argc;
char **argv;
#endif
{
int c;
struct hostent *hp;
struct in_addr addr;
char *rplaydrc;
starttime = time(0);
progname = argv[0];
servers_file = tilde_expand(RPLAY_SERVERS);
#ifdef HAVE_HELPERS
helpers_file = tilde_expand(RPLAY_HELPERS);
#endif /* HAVE_HELPERS */
sounds_file = tilde_expand(RPLAY_CONF);
#ifdef AUTH
hosts_file = tilde_expand(RPLAY_HOSTS);
#endif /* AUTH */
cache_dir = tilde_expand(RPLAY_CACHE);
log_file = tilde_expand(RPLAY_LOG);
#ifdef RPLAYD_ALWAYS_LOG
report_level = RPLAYD_LOG_LEVEL;
if (report_level != REPORT_NONE)
{
logging++;
}
#else
report_level = REPORT_ERROR;
#endif
/* Parse RPLAYDRC. */
rplaydrc = tilde_expand(RPLAYDRC);
if (rplaydrc)
{
struct stat st;
if (stat(rplaydrc, &st) == 0 && S_ISREG(st.st_mode))
{
do_option_file(rplaydrc);
}
free(rplaydrc);
}
/* Parse command line options. */
while ((c = getopt_long(argc, argv, OPTIONS, longopts, 0)) != -1)
{
do_option(c);
}
if (audio_test_mode)
{
report_level = 0; /* don't want debug output */
audio_test();
done(0);
}
if (run_as_group && *run_as_group)
{
struct group *gr = getgrnam(run_as_group);
if (!gr)
{
fprintf(stderr, "Unknown group `%s'.\n", run_as_group);
done(1);
}
if (setgid(gr->gr_gid) < 0)
{
report(REPORT_ERROR, "setgid: %s\n", sys_err_str(errno));
done(1);
}
report(REPORT_DEBUG, "running as group %s (%d)\n", run_as_group, gr->gr_gid);
}
if (run_as_user && *run_as_user)
{
struct passwd *pw = getpwnam(run_as_user);
if (!pw)
{
fprintf(stderr, "Unknown user `%s'.\n", run_as_user);
done(1);
}
if (setuid(pw->pw_uid) < 0)
{
report(REPORT_ERROR, "setuid: %s\n", sys_err_str(errno));
done(1);
}
report(REPORT_DEBUG, "running as user %s (%d)\n", run_as_user, pw->pw_uid);
}
if (debug)
{
logging = 0;
do_fork = 0;
}
/* Fork */
if (do_fork)
{
int pid = fork();
if (pid != 0)
{
exit(0);
}
}
if (gethostname(hostname, sizeof(hostname)) < 0)
{
report(REPORT_ERROR, "gethostname: %s\n", sys_err_str(errno));
done(1);
}
hp = gethostbyname(hostname);
if (hp == NULL)
{
report(REPORT_ERROR, "gethostbyname: cannot resolve hostname: %s\n", hostname);
done(1);
}
memcpy((char *) &addr, (char *) hp->h_addr, hp->h_length);
hostaddr = strdup(inet_ntoa(addr));
rplayd_pid = getpid();
handle_signals();
/* Call all the initialization routines. */
rplayd_init();
#ifdef HAVE_HELPERS
helper_read(helpers_file);
#endif /* HAVE_HELPERS */
sound_read(sounds_file);
cache_init(cache_dir);
cache_read();
#ifdef AUTH
host_read(hosts_file);
#endif /* AUTH */
server_read(servers_file);
spool_init();
timer_init();
if (rplayd_audio_init() < 0)
{
/* Disable audio. */
audio_enabled = 0;
report(REPORT_DEBUG, "audio disabled\n");
}
else
{
/* Save the initial audio configuration. */
default_sample_rate = rplay_audio_sample_rate;
default_precision = rplay_audio_precision;
default_channels = rplay_audio_channels;
default_format = rplay_audio_format;
default_port = rplay_audio_port;
/* Close audio device to avoid hogging it. */
rplay_audio_close();
}
#ifdef AUTH
report(REPORT_DEBUG, "authentication %s\n", auth_enabled ? "enabled" : "disabled");
#else
report(REPORT_DEBUG, "no authentication\n");
#endif
if (forward)
{
report(REPORT_DEBUG, "forwarding sounds to %s\n", forward);
}
report(REPORT_DEBUG, "%s rplayd %s ready.\n", hostname, RPLAY_VERSION);
doit();
}
#ifdef __STDC__
static void
do_option_file(char *option_file)
#else
static void
do_option_file(option_file)
char *option_file;
#endif
{
int first, c;
char buf[BUFSIZ], *p;
FILE *fp;
int saved_optind;
char *argv[1024]; /* that should be enough */
int argc = 0;
fp = fopen(option_file, "r");
if (fp == NULL)
{
report(REPORT_ERROR, "cannot open `%s'.\n", option_file);
return;
}
argv[argc++] = progname;
tilde_additional_prefixes = (char **) xmalloc(2 * sizeof(char *));
tilde_additional_prefixes[0] = "=~";
tilde_additional_prefixes[1] = (char *) NULL;
while (fgets(buf, sizeof(buf), fp))
{
switch (buf[0])
{
case '#':
case ' ':
case '\n':
case '\t':
continue;
}
first = 1;
while (p = (char *) strtok(first ? buf : NULL, " \t\n"))
{
first = 0;
argv[argc++] = tilde_expand(p);
}
}
fclose(fp);
free((char *) tilde_additional_prefixes);
tilde_additional_prefixes = NULL;
argv[argc] = NULL;
saved_optind = optind; /* save optind */
optind = 1; /* reset optind */
while ((c = getopt_long(argc, argv, OPTIONS, longopts, 0)) != -1)
{
do_option(c);
}
optind = saved_optind; /* restore optind */
}
#ifdef __STDC__
static void
do_option(int option_value)
#else
static void
do_option(option_value)
int option_value;
#endif
{
static int within_file;
int i;
char *p;
switch (option_value)
{
case 0:
/* getopt has processed a long-named option -- do nothing */
break;
case 1:
/* --audio-channels */
optional_channels = atoi(optarg);
if (optional_channels != 1 && optional_channels != 2)
{
usage();
done(1);
}
break;
case 2:
/* --help */
usage();
done(1);
case 3:
/* --audio-format */
optional_format = string_to_audio_format(optarg);
if (optional_format < 0)
{
usage();
done(1);
}
break;
case 4:
/* --memory-cache-size */
sound_cache_max_size = atoi(optarg);
break;
case 5:
/* --memory-cache-sound-size */
sound_cache_max_sound_size = atoi(optarg);
break;
case 6:
/* --port or --rplay-port */
rplay_port = atoi(optarg);
break;
case 7:
/* --rptp-port */
rptp_port = atoi(optarg);
break;
#ifdef OTHER_RPLAY_PORTS
case 8:
/* --other-rplay-port */
other_rplay_port = atoi(optarg);
break;
case 9:
/* --other-rptp-port */
other_rptp_port = atoi(optarg);
break;
#endif /* OTHER_RPTP_PORTS */
case 10:
/* --audio-port */
if (strstr(optarg, "none"))
{
SET_BIT(optional_port, RPLAY_AUDIO_PORT_NONE);
}
if (strstr(optarg, "speaker"))
{
SET_BIT(optional_port, RPLAY_AUDIO_PORT_SPEAKER);
}
if (strstr(optarg, "headphone"))
{
SET_BIT(optional_port, RPLAY_AUDIO_PORT_HEADPHONE);
}
if (strstr(optarg, "lineout"))
{
SET_BIT(optional_port, RPLAY_AUDIO_PORT_LINEOUT);
}
break;
case 11:
do_option_file(optarg);
break;
case 12: /* --cache-remove */
cache_remove++;
break;
case 13: /* --audio-info-ulaw, --info-ulaw */
optional_format = RPLAY_FORMAT_ULAW;
optional_sample_rate = 8000;
optional_precision = 8;
optional_channels = 1;
break;
#ifdef HAVE_CDROM
case 14: /* cdrom0 */
for (i = 0; i < MAX_CDROMS; i++)
{
if (strncmp(cdrom_table[i].name, "cdrom0", 6) == 0)
{
cdrom_table[i].device = optarg;
break;
}
}
break;
case 15: /* cdrom1 */
for (i = 0; i < MAX_CDROMS; i++)
{
if (strncmp(cdrom_table[i].name, "cdrom1", 6) == 0)
{
cdrom_table[i].device = optarg;
break;
}
}
break;
case 16: /* cdrom2 */
for (i = 0; i < MAX_CDROMS; i++)
{
if (strncmp(cdrom_table[i].name, "cdrom2", 6) == 0)
{
cdrom_table[i].device = optarg;
break;
}
}
break;
case 17: /* cdrom3 */
for (i = 0; i < MAX_CDROMS; i++)
{
if (strncmp(cdrom_table[i].name, "cdrom3", 6) == 0)
{
cdrom_table[i].device = optarg;
break;
}
}
break;
#endif /* HAVE_CDROM */
#ifdef HAVE_OSS
case 18: /* audio-fragsize */
optional_fragsize = atoi(optarg);
break;
#endif /* HAVE_OSS */
case 19: /* fork */
do_fork++;
break;
case 20: /* helpers */
helpers_file = optarg;
break;
case 21: /* group */
run_as_group = optarg;
break;
case 22: /* user */
run_as_user = optarg;
break;
case 'A':
rplay_audio_device = optarg;
break;
#ifndef HAVE_OSS
case 'b':
rplay_audio_bufsize = atoi(optarg);
break;
#endif /* !HAVE_OSS */
case 'B':
optional_precision = atoi(optarg);
if (optional_precision != 8 && optional_precision != 16)
{
usage();
done(1);
}
break;
case 'c':
rplay_audio_timeout = atoi(optarg);
break;
case 'C':
sounds_file = optarg;
break;
case 'd':
debug++;
report_level = REPORT_DEBUG;
break;
case 'D':
cache_dir = optarg;
break;
case 'f':
forward = optarg;
break;
case 'F':
rplay_audio_flush_timeout = atoi(optarg);
break;
#ifdef AUTH
case 'H':
hosts_file = optarg;
break;
#endif /* AUTH */
case 'i': /* --audio-info, --info */
/* Example: ulaw,8000,8,1 */
p = strtok(optarg, ", ");
if (p)
optional_format = string_to_audio_format(p);
p = strtok(NULL, ",");
if (p)
optional_sample_rate = atoi(p);
p = strtok(NULL, ",");
if (p)
optional_precision = atoi(p);
p = strtok(NULL, ",");
if (p)
optional_channels = atoi(p);
break;
case 'l':
logging++;
report_level = atoi(optarg);
break;
case 'L':
log_file = optarg;
break;
case 'n':
inetd = 0;
break;
case 'N':
audio_enabled = 0;
break;
case 'r':
rplay_audio_rate = atoi(optarg);
break;
case 'R':
optional_sample_rate = atoi(optarg);
break;
case 's':
cache_max_size = atoi(optarg);
break;
case 'S':
servers_file = optarg;
break;
case 't':
rplayd_timeout = atoi(optarg);
break;
case 'T':
rptp_timeout = atoi(optarg);
break;
case 'v':
printf("rplay %s\n", RPLAY_VERSION);
done(0);
break;
default:
fprintf(stderr, "Try `%s --help' for more information.\n", progname);
done(1);
}
}
/*
* The main loop of rplayd.
*/
static void
doit()
{
int nfds, icount = 0;
int idle = 1;
struct timeval select_timeout;
fd_set rfds, wfds;
SPOOL *sp;
/* Initialize the fd_sets before starting. */
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&read_mask);
FD_ZERO(&write_mask);
FD_SET(rplay_fd, &read_mask);
FD_SET(rptp_fd, &read_mask);
#ifdef OTHER_RPLAY_PORTS
FD_SET(other_rplay_fd, &read_mask);
FD_SET(other_rptp_fd, &read_mask);
#endif
for (;;)
{
if (need_to_reset)
{
reset();
need_to_reset = 0;
}
if (audio_enabled)
{
if (timer_enabled)
{
if (!spool_nplaying)
{
timer_stop();
rplay_audio_size = 0;
if (rplay_audio_flush_timeout == -1)
{
report(REPORT_DEBUG, "flushing %s\n", rplay_audio_device);
rplay_audio_flush();
}
/* Notify the final zero levels. */
rplay_audio_left_level = 0;
rplay_audio_right_level = 0;
if (connection_want_level_notify)
{
connection_notify(0, NOTIFY_LEVEL, 1); /* force notification */
}
}
else
{
/* keep trying to open */
if (!rplay_audio_isopen())
{
rplay_audio_open();
}
if (connection_level_notify)
{
connection_notify(0, NOTIFY_LEVEL, 0);
connection_level_notify = 0;
}
for (sp = spool; sp; sp = sp->next)
{
if (sp->notify_position)
{
connection_notify(0, NOTIFY_POSITION, sp);
sp->notify_position = 0;
}
}
if (monitor_count && monitor_update)
{
connection_monitor_continue();
monitor_update = 0;
}
}
}
else if (spool_nplaying)
{
if (!rplay_audio_isopen())
{
report(REPORT_DEBUG, "opening %s\n", rplay_audio_device);
if (rplay_audio_open() < 0)
{
report(REPORT_ERROR,
"rplay_audio_open: %s: %s (will keep trying)\n",
rplay_audio_device, sys_err_str(errno));
}
}
/* start the timer even if the audio device isn't open */
timer_start(curr_rate);
}
if (spool_needs_update)
{
spool_update();
}
}
if (connections)
{
connection_check_timeout();
}
rfds = read_mask;
wfds = write_mask;
if (idle)
{
report(REPORT_DEBUG, "entering idle mode\n");
select_timeout.tv_sec = rplayd_timeout;
select_timeout.tv_usec = 0;
#ifdef __hpux
nfds = select(FD_SETSIZE, (int *) &rfds, (int *) &wfds, 0,
rplayd_timeout ? &select_timeout : 0);
#else
nfds = select(FD_SETSIZE, &rfds, &wfds, 0,
rplayd_timeout ? &select_timeout : 0);
#endif
if (nfds == 0)
{
done(0);
}
#ifdef AUTO_REREAD
#ifdef HAVE_HELPERS
helper_stat(helpers_file);
#endif /* HAVE_HELPERS */
sound_stat(sounds_file);
#ifdef AUTH
host_stat(hosts_file);
#endif /* AUTH */
server_stat(servers_file);
#endif /* AUTO_REREAD */
idle = 0;
icount = 0;
}
else
{
select_timeout.tv_sec = 1;
select_timeout.tv_usec = 0;
#ifdef __hpux
nfds = select(FD_SETSIZE, (int *) &rfds, (int *) &wfds, 0,
&select_timeout);
#else
nfds = select(FD_SETSIZE, &rfds, &wfds, 0,
&select_timeout);
#endif
}
switch (nfds)
{
case -1:
if (errno != EINTR && errno != EAGAIN)
{
report(REPORT_ERROR, "select: %s\n", sys_err_str(errno));
done(1);
}
break;
case 0:
icount++;
/*
* Timeouts that occur when no sounds are playing.
*/
if (!spool_nplaying && !rplay_audio_size)
{
if (audio_enabled && rplay_audio_timeout && icount == rplay_audio_timeout
&& rplay_audio_isopen())
{
report(REPORT_DEBUG, "closing %s\n", rplay_audio_device);
rplay_audio_close();
}
if (audio_enabled && rplay_audio_flush_timeout > 0
&& icount == rplay_audio_flush_timeout && rplay_audio_isopen())
{
report(REPORT_DEBUG, "flushing %s\n", rplay_audio_device);
rplay_audio_flush();
}
if (sound_cleanup_timeout && icount == sound_cleanup_timeout)
{
sound_cleanup();
}
if (buffer_cleanup_timeout && icount == buffer_cleanup_timeout)
{
buffer_cleanup();
}
if (spool_cleanup_timeout && icount == spool_cleanup_timeout)
{
spool_cleanup();
}
/*
* See if rplayd should enter idle mode.
* Idle mode should only be entered when *nothing*
* is happening.
*/
if (icount >= rplay_audio_timeout
&& connection_idle()
&& icount >= rplay_audio_flush_timeout
&& icount >= sound_cleanup_timeout
&& icount >= buffer_cleanup_timeout
&& icount >= spool_cleanup_timeout)
{
idle = 1;
if (monitor_buffers)
{
if (!monitor_count)
{
report(REPORT_DEBUG, "cleaning monitor buffers - %d bytes\n",
monitor_total_bytes);
monitor_cleanup();
}
else
{
report(REPORT_DEBUG, "keeping monitor buffers - %d bytes\n",
monitor_total_bytes);
}
}
}
}
break;
default:
if (FD_ISSET(rplay_fd, &rfds))
{
rplayd_read(rplay_fd);
}
#ifdef OTHER_RPLAY_PORTS
if (FD_ISSET(other_rplay_fd, &rfds))
{
rplayd_read(other_rplay_fd);
}
#endif
connection_update(&rfds, &wfds);
#if defined(HAVE_CDROM) || defined(HAVE_HELPERS)
rplayd_pipe_read(&rfds);
#endif
icount = 0;
break;
}
}
}
/*
* Set up the sockets to receive RPLAY packets and RPTP connections.
*/
void
rplayd_init()
{
struct sockaddr_in s;
struct servent *sp;
if (inetd)
{
rplay_fd = 0;
#ifdef OTHER_RPLAY_PORTS
/* Try to figure out which port is being used by inetd. */
sp = getservbyname("rplay", "udp");
if (sp == NULL)
{
report(REPORT_ERROR, "can't find rplay/udp service\n");
done(1);
}
if (ntohs(sp->s_port) == RPLAY_PORT)
{
other_rplay_fd = udp_socket(other_rplay_port ? other_rplay_port : OLD_RPLAY_PORT);
}
else
{
other_rplay_fd = udp_socket(other_rplay_port ? other_rplay_port : RPLAY_PORT);
}
#endif
}
else
{
rplay_fd = udp_socket(rplay_port ? rplay_port : RPLAY_PORT);
#ifdef OTHER_RPLAY_PORTS
other_rplay_fd = udp_socket(other_rplay_port ? other_rplay_port : OLD_RPLAY_PORT);
#endif
}
/*
* Make the RPLAY sockets non-blocking.
*/
fd_nonblock(rplay_fd);
#ifdef OTHER_RPLAY_PORTS
fd_nonblock(other_rplay_fd);
#endif
if (forward)
{
unsigned long addr = inet_addr(forward);
memset((char *) &s, 0, sizeof(s));
s.sin_family = AF_INET;
s.sin_port = htons(RPLAY_PORT);
if (addr == 0xffffffff)
{
struct hostent *hp = gethostbyname(forward);
if (hp == NULL)
{
report(REPORT_ERROR, "gethostbyname: %s unknown host\n", forward);
done(1);
}
memcpy((char *) &s.sin_addr.s_addr, (char *) hp->h_addr, hp->h_length);
}
else
{
memcpy((char *) &s.sin_addr.s_addr, (char *) &addr, sizeof(addr));
}
forward_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (forward_fd < 0)
{
report(REPORT_ERROR, "socket: %s\n", sys_err_str(errno));
done(1);
}
if (connect(forward_fd, (struct sockaddr *) &s, sizeof(s)) < 0)
{
report(REPORT_ERROR, "connect: %s\n", sys_err_str(errno));
done(1);
}
}
rptp_fd = tcp_socket(rptp_port ? rptp_port : RPTP_PORT);
#ifdef OTHER_RPLAY_PORTS
other_rptp_fd = tcp_socket(other_rptp_port ? other_rptp_port : OLD_RPTP_PORT);
#endif
/*
* Make the RPTP sockets non-blocking.
*/
fd_nonblock(rptp_fd);
#ifdef OTHER_RPLAY_PORTS
fd_nonblock(other_rptp_fd);
#endif
}
int
rplayd_audio_init()
{
int sample_size;
if (!audio_enabled)
{
return -1;
}
if (optional_sample_rate && optional_sample_rate > MAX_SAMPLE_RATE)
{
optional_sample_rate = MAX_SAMPLE_RATE;
}
/* Call rplay_audio_init() which sets all the rplay_audio variables. */
rplay_audio_table = NULL; /* will be set by rplay_audio_init() */
if (rplay_audio_init() < 0)
{
return -1;
}
/* XXX */
optional_sample_rate = rplay_audio_sample_rate;
optional_precision = rplay_audio_precision;
optional_channels = rplay_audio_channels;
optional_format = rplay_audio_format;
optional_port = rplay_audio_port;
sample_size = (rplay_audio_precision >> 3) * rplay_audio_channels;
if (rplay_audio_match || rplay_audio_rate == 0)
{
rplay_audio_rate = RPLAY_AUDIO_RATE;
}
curr_rate = rplay_audio_rate;
#ifndef HAVE_OSS
/* Click prevention: Make sure curr_rate won't split up samples.
If it will, it's easiest right now to modify the rate and not
the sample_rate. */
if (rplay_audio_sample_rate % curr_rate)
{
while (rplay_audio_sample_rate % curr_rate)
{
curr_rate--; /* Slow it down. */
}
report(REPORT_DEBUG, "changed curr_rate from %d to %d\n",
rplay_audio_rate, curr_rate);
}
if (rplay_audio_match || rplay_audio_bufsize == 0)
{
rplay_audio_bufsize = rplay_audio_sample_rate / curr_rate * sample_size;
}
curr_bufsize = rplay_audio_bufsize;
/* More click prevention: Make sure curr_bufsize won't split up samples.
If it will, curr_bufsize should be increased, not decreased. */
if (curr_bufsize % sample_size)
{
while (curr_bufsize % sample_size)
{
curr_bufsize++; /* Add a little. */
}
report(REPORT_DEBUG, "changed curr_bufsize from %d to %d\n",
rplay_audio_bufsize, curr_bufsize);
}
#endif /* !HAVE_OSS */
/*
* Create the audio buffer which holds at most 1 second of 16-bit
* stereo audio data sampled at MAX_SAMPLE_RATE Hz.
*/
max_rplay_audio_bufsize = MAX_SAMPLE_RATE * 2 /* 16-bit */ * 2 /* stereo */ ;
if (rplay_audio_buf)
{
free((char *) rplay_audio_buf);
}
rplay_audio_buf = (char *) malloc(max_rplay_audio_bufsize);
if (rplay_audio_buf == NULL)
{
report(REPORT_ERROR, "cannot allocate rplay_audio_buf, size=%d\n",
max_rplay_audio_bufsize);
done(1);
}
/*
* Update the volume of the audio device.
*/
rplay_audio_volume = rplay_audio_get_volume();
if (rplay_audio_volume < 0)
{
rplay_audio_volume = 0;
}
report(REPORT_DEBUG, "\
audio: bits=%d sample-rate=%d channels=%d format=%s volume=%d port-mask=%0x\n",
rplay_audio_precision, rplay_audio_sample_rate, rplay_audio_channels,
audio_format_to_string(rplay_audio_format), rplay_audio_volume, rplay_audio_port);
#ifdef HAVE_OSS
report(REPORT_DEBUG, "timer: rate=%d, min bytes/sec=%d\n", curr_rate,
(rplay_audio_sample_rate / curr_rate * sample_size));
#else
report(REPORT_DEBUG, "timer: rate=%d, bufsize=%d\n", curr_rate, curr_bufsize);
#endif
return 0;
}
/* Attempt to match the audio device configuration with the sound
in `sp'. Currenly only the sample-rate is matched. */
#ifdef __STDC__
void
rplayd_audio_match(SPOOL *match_sp)
#else
void
rplayd_audio_match(match_sp)
SPOOL *match_sp;
#endif
{
if (rplay_audio_match
&& spool_nplaying == 0 /* Only is nothing else is playing. */
&& match_sp->sample_rate != rplay_audio_sample_rate)
{
RPLAY_AUDIO_TABLE *t;
SPOOL *sp;
int prev_sample_rate;
/* Find the closest matching table entry. */
optional_sample_rate = match_sp->sample_rate;
prev_sample_rate = rplay_audio_table ? rplay_audio_table->sample_rate : 0;
for (t = rplay_audio_table; t; t++)
{
if (t->sample_rate == 0
|| optional_sample_rate < prev_sample_rate)
{
optional_sample_rate = prev_sample_rate;
break;
}
else if (optional_sample_rate >= prev_sample_rate
&& optional_sample_rate <= t->sample_rate)
{
if ((t->sample_rate - optional_sample_rate)
> (optional_sample_rate - prev_sample_rate))
{
optional_sample_rate = prev_sample_rate;
}
else
{
optional_sample_rate = t->sample_rate;
}
break;
}
prev_sample_rate = t->sample_rate;
}
report(REPORT_DEBUG, "matched %d with %d\n", match_sp->sample_rate,
optional_sample_rate);
if (rplayd_audio_init() < 0)
{
/* That sample rate didn't work -- Use the default instead. */
optional_sample_rate = default_sample_rate;
rplayd_audio_init();
}
if (timer_enabled)
{
timer_restart(curr_rate);
}
/* Update the spool entries with the new sample-rate. */
for (sp = spool; sp; sp = sp->next)
{
sp->sample_factor = (double) sp->sample_rate / (double) rplay_audio_sample_rate;
}
}
}
monitor_alloc()
{
if (!monitor_buffers)
{
BUFFER *b, *end;
monitor_buffers = buffer_alloc(monitor_total_bytes, BUFFER_KEEP);
for (b = monitor_buffers; b; b = b->next)
{
/* these will always be full buffers */
b->nbytes = BUFFER_SIZE;
if (!b->next)
{
end = b;
}
}
/* make the list of buffer cyclic */
end->next = monitor_buffers;
}
monitor_update = 0;
}
monitor_cleanup()
{
BUFFER *b, *b_next;
for (b = monitor_buffers; b->next != monitor_buffers; b = b->next);
b->next = NULL;
buffer_dealloc(monitor_buffers, 1);
monitor_buffers = NULL;
}
/*
* Write nbytes of audio data from the audio buffer to the audio device.
*/
#ifdef __STDC__
void
rplayd_write(int nbytes)
#else
void
rplayd_write(nbytes)
int nbytes;
#endif
{
int nwritten = 0;
/* Insert `nbytes' of audio data from the spool into `rplay_audio_buf'. */
spool_process(rplay_audio_buf, nbytes);
/*
* Write the audio data if the audio device is open.
* Otherwise forget about the data.
*/
if (rplay_audio_size && rplay_audio_isopen())
{
nwritten = rplay_audio_write(rplay_audio_buf, rplay_audio_size);
#if 0
report(REPORT_DEBUG, "audio_write: %d -> %d\n", rplay_audio_size, nwritten);
#endif
/* Flush after every write? */
if (rplay_audio_flush_timeout == -2)
{
report(REPORT_DEBUG, "flushing %s\n", rplay_audio_device);
rplay_audio_flush();
}
}
if (monitor_count)
{
char *buf = rplay_audio_buf;
int size = rplay_audio_size;
int n;
while (size)
{
n = MIN(BUFFER_SIZE - monitor_buffers->offset, size);
memcpy(monitor_buffers->buf + monitor_buffers->offset, buf, n);
monitor_buffers->offset += n;
buf += n;
size -= n;
if (monitor_buffers->offset == BUFFER_SIZE)
{
monitor_buffers = monitor_buffers->next;
monitor_buffers->offset = 0;
monitor_update++;
}
}
}
rplay_audio_size = 0;
/* Flush when the audio stream has ended. */
if (spool_nplaying == 0 && rplay_audio_size == 0)
{
if (rplay_audio_flush_timeout == -1)
{
report(REPORT_DEBUG, "flushing %s\n", rplay_audio_device);
rplay_audio_flush();
}
}
}
/*
* Read RPLAY packets from the UDP socket.
*/
#ifdef __STDC__
void
rplayd_read(int fd)
#else
void
rplayd_read(fd)
int fd;
#endif
{
static char recv_buf[MAX_PACKET];
char *packet;
struct sockaddr_in f;
int j, n, flen;
RPLAY *rp;
for (j = 0; j < SPOOL_SIZE; j++)
{
flen = sizeof(f);
n = recvfrom(fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &f, &flen);
if (n <= 0)
{
continue;
}
if (forward)
{
write(forward_fd, recv_buf, n);
continue;
}
#ifdef AUTH
if (!host_access(f, HOST_EXECUTE))
{
if (!host_access(f, HOST_READ) && !host_access(f, HOST_WRITE))
{
report(REPORT_NOTICE, "%s permission denied\n", inet_ntoa(f.sin_addr));
}
continue;
}
#endif /* AUTH */
packet = recv_buf;
switch (*packet)
{
#ifdef OLD_RPLAY
case OLD_RPLAY_PLAY:
case OLD_RPLAY_STOP:
case OLD_RPLAY_PAUSE:
case OLD_RPLAY_CONTINUE:
packet = rplay_convert(recv_buf);
#endif /* OLD_RPLAY */
case RPLAY_PACKET_ID:
rp = rplay_unpack(packet);
if (rp == NULL)
{
report(REPORT_ERROR, "rplay_unpack: %s\n", rplay_errlist[rplay_errno]);
break;
}
switch (rp->command)
{
case RPLAY_PING:
#ifdef DEBUG
report(REPORT_DEBUG, "received a ping packet\n");
#endif
rplay_destroy(rp);
break;
case RPLAY_PLAY:
report(REPORT_INFO, "%s play %s\n",
inet_ntoa(f.sin_addr),
(char *) rplay_get(rp, RPLAY_SOUND, 0));
rplayd_play(rp, f);
break;
case RPLAY_STOP:
report(REPORT_INFO, "%s stop %s\n",
inet_ntoa(f.sin_addr),
(char *) rplay_get(rp, RPLAY_SOUND, 0));
rplayd_stop(rp, f);
break;
case RPLAY_PAUSE:
report(REPORT_INFO, "%s pause %s\n",
inet_ntoa(f.sin_addr),
(char *) rplay_get(rp, RPLAY_SOUND, 0));
rplayd_pause(rp, f);
break;
case RPLAY_CONTINUE:
report(REPORT_INFO, "%s continue %s\n",
inet_ntoa(f.sin_addr),
(char *) rplay_get(rp, RPLAY_SOUND, 0));
rplayd_continue(rp, f);
break;
case RPLAY_RESET:
report(REPORT_INFO, "received a reset packet\n");
need_reset();
rplay_destroy(rp);
break;
case RPLAY_DONE:
report(REPORT_INFO, "%s done %s\n",
inet_ntoa(f.sin_addr),
(char *) rplay_get(rp, RPLAY_SOUND, 0));
rplayd_done(rp, f);
break;
case RPLAY_PUT:
report(REPORT_INFO, "%s put id=%d sequence=%d size=%d\n",
inet_ntoa(f.sin_addr),
rp->id,
rp->sequence,
rp->data_size);
rplayd_put(rp, f);
break;
}
break;
default:
report(REPORT_ERROR, "unknown RPLAY packet received from %s\n",
inet_ntoa(f.sin_addr));
break;
}
}
}
#if defined(HAVE_CDROM) || defined(HAVE_HELPERS)
/* Read data from piped devices and insert it into the spool entry.
This is done using rplayd main select loop for efficiency. */
#ifdef __STDC__
void
rplayd_pipe_read(fd_set * rfds)
#else
void
rplayd_pipe_read(rfds)
fd_set *rfds;
#endif
{
SPOOL *sp, *sp_next;
extern BUFFER *sound_pipe_read(SINDEX *si);
for (sp = spool; sp; sp = sp_next)
{
sp_next = sp->next;
if (!sp->si)
{
continue;
}
#ifdef HAVE_CDROM
if (sp->si->sound->type == SOUND_CDROM && FD_ISSET(sp->si->fd, rfds))
{
BUFFER *b = sound_pipe_read(sp->si);
if (b)
{
spool_flow_insert(sp, b);
}
}
#endif
#ifdef HAVE_HELPERS
if (sp->si->sound->needs_helper && FD_ISSET(sp->si->fd, rfds))
{
BUFFER *b = sound_pipe_read(sp->si);
if (b)
{
spool_flow_insert(sp, b);
}
}
#endif
}
}
#endif
#ifdef __STDC__
int
rplayd_play(RPLAY *rp, struct sockaddr_in sin)
#else
int
rplayd_play(rp, sin)
RPLAY *rp;
struct sockaddr_in sin;
#endif
{
SPOOL *sp = NULL;
int i, n;
int start_sound, end_sound, merged_lists;
RPLAY_ATTRS *attrs;
if (!audio_enabled)
{
return -1;
}
/* If a list_name is specified, try to find a matching list. */
if (*rp->list_name)
{
for (sp = spool; sp; sp = sp->next)
{
if (*sp->rp->list_name
&& strcmp(rp->list_name, sp->rp->list_name) == 0)
{
break;
}
}
if (sp)
{
*(sp->rp->attrsp) = rp->attrs; /* Append the new attrs. */
sp->rp->attrsp = rp->attrsp;
start_sound = sp->rp->nsounds;
sp->rp->nsounds += rp->nsounds;
end_sound = sp->rp->nsounds;
merged_lists = 1;
}
}
/* Find a spool entry. */
if (!sp)
{
/* Check the priority level. rplayd will ignore all sounds
with priorities less than rplay_priority_threshold. */
if (rp->priority < rplay_priority_threshold)
{
report(REPORT_DEBUG, "sound at priority %d ignored\n",
rp->priority);
rplay_destroy(rp);
return -1;
}
sp = spool_next(rp->priority);
if (!sp)
{
report(REPORT_DEBUG, "spool full\n");
rplay_destroy(rp);
return -1;
}
start_sound = 0;
end_sound = rp->nsounds;
merged_lists = 0;
}
/* Gotta have attributes! */
if (!rp->attrs)
{
report(REPORT_DEBUG, "`rp' missing attributes\n");
rplay_destroy(rp);
spool_destroy(sp);
return -1;
}
/* Add the sounds to the spool entry. */
n = 0;
for (i = start_sound, attrs = rp->attrs;
i < end_sound && attrs;
i++, attrs = attrs->next)
{
if (attrs->rptp_server)
{
SERVER *s;
s = (SERVER *) malloc(sizeof(SERVER));
s->next = NULL;
s->sin.sin_family = AF_INET;
s->sin.sin_port = htons(attrs->rptp_server_port);
s->sin.sin_addr.s_addr = inet_addr(attrs->rptp_server);
sp->sound[i] = sound_lookup(attrs->sound,
attrs->rptp_search ? SOUND_FIND : SOUND_DONT_FIND, s);
}
else
{
sp->sound[i] = sound_lookup(attrs->sound,
attrs->rptp_search ? SOUND_FIND : SOUND_DONT_FIND, NULL);
}
if (sp->sound[i] == NULL)
{
rplay_destroy(rp);
spool_destroy(sp);
return -1;
}
if (sp->sound[i]->status != SOUND_READY)
{
n++;
}
}
/* Set-up the spool entry only if it's a new sound list. */
if (!merged_lists)
{
sp->rp = rp;
sp->curr_attrs = rp->attrs;
sp->curr_sound = 0;
sp->curr_count = sp->curr_attrs->count;
sp->list_count = rp->count;
sp->sin = sin;
if (n)
{
sp->state = SPOOL_WAIT;
}
else
{
spool_play(sp);
}
}
return sp->id;
}
#ifdef __STDC__
int
rplayd_stop(RPLAY *rp, struct sockaddr_in sin)
#else
int
rplayd_stop(rp, sin)
RPLAY *rp;
struct sockaddr_in sin;
#endif
{
int n;
if (!audio_enabled)
{
rplay_destroy(rp);
return -1;
}
n = spool_match(rp, spool_stop, sin);
rplay_destroy(rp);
return n > 0 ? 0 : -1;
}
#ifdef __STDC__
int
rplayd_pause(RPLAY *rp, struct sockaddr_in sin)
#else
int
rplayd_pause(rp, sin)
RPLAY *rp;
struct sockaddr_in sin;
#endif
{
int n;
if (!audio_enabled)
{
rplay_destroy(rp);
return -1;
}
n = spool_match(rp, spool_pause, sin);
rplay_destroy(rp);
return n > 0 ? 0 : -1;
}
#ifdef __STDC__
int
rplayd_continue(RPLAY *rp, struct sockaddr_in sin)
#else
int
rplayd_continue(rp, sin)
RPLAY *rp;
struct sockaddr_in sin;
#endif
{
int n;
if (!audio_enabled)
{
rplay_destroy(rp);
return -1;
}
n = spool_match(rp, spool_continue, sin);
rplay_destroy(rp);
return n > 0 ? 0 : -1;
}
#ifdef __STDC__
int
rplayd_done(RPLAY *rp, struct sockaddr_in sin)
#else
int
rplayd_done(rp, sin)
RPLAY *rp;
struct sockaddr_in sin;
#endif
{
int n;
if (!audio_enabled)
{
rplay_destroy(rp);
return -1;
}
n = spool_match(rp, spool_done, sin);
rplay_destroy(rp);
return n > 0 ? 0 : -1;
}
#ifdef __STDC__
int
rplayd_put(RPLAY *rp, struct sockaddr_in sin)
#else
int
rplayd_put(rp, sin)
RPLAY *rp;
struct sockaddr_in sin;
#endif
{
BUFFER *b;
SPOOL *sp;
if (!audio_enabled)
{
rplay_destroy(rp);
return -1;
}
/* Find the spool entry. */
sp = spool_find(rp->id);
if (!sp)
{
report(REPORT_DEBUG, "put: `%d' no such spool id\n", rp->id);
rplay_destroy(rp);
return -1;
}
/* Create a buffer and insert the flow data. */
b = buffer_create();
b->nbytes = rp->data_size;
memcpy(b->buf, rp->data, b->nbytes);
spool_flow_insert(sp, b);
rplay_destroy(rp);
return 0;
}
BUFFER *
rplayd_status()
{
BUFFER *b;
b = buffer_create();
b->buf[0] = RPTP_OK;
b->buf[1] = '\0';
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), "host=%s", hostname);
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " version=%s", RPLAY_VERSION);
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " uptime=%s", time2string(time(0) - starttime));
if (audio_enabled)
{
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-bits=%d", rplay_audio_precision);
#ifndef HAVE_OSS
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-bufsize=%d", rplay_audio_bufsize);
#endif /* !HAVE_OSS */
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-byte-order=%s",
byte_order_to_string(RPLAY_AUDIO_BYTE_ORDER));
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-channels=%d", rplay_audio_channels);
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-device=%s", rplay_audio_device);
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-format=%s",
audio_format_to_string(rplay_audio_format));
#ifdef HAVE_OSS
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-fragsize=%d", rplay_audio_fragsize);
#endif /* HAVE_OSS */
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-port=%s",
audio_port_to_string(rplay_audio_port));
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-rate=%d", rplay_audio_rate);
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-sample-rate=%d", rplay_audio_sample_rate);
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " volume=%d", rplay_audio_volume);
#ifndef HAVE_OSS
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " curr-bufsize=%d", curr_bufsize);
#endif /* !HAVE_OSS */
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " curr-rate=%d", curr_rate);
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " priority-threshold=%d",
rplay_priority_threshold);
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-close=%d",
rplay_audio_timeout);
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), " audio-device-status=%s",
rplay_audio_isopen() ? "open" : "closed");
}
SNPRINTF(SIZE(b->buf + strlen(b->buf), BUFFER_SIZE), "\r\n");
b->nbytes = strlen(b->buf);
return b;
}
void
need_reset()
{
need_to_reset++;
}
/* Reset rplayd's state. */
static void
reset()
{
int was_enabled = timer_enabled;
if (was_enabled)
{
timer_stop();
}
spool_init();
connection_cleanup();
#ifdef HAVE_HELPERS
helper_reread(helpers_file);
#endif /* HAVE_HELPERS */
sound_reread(sounds_file);
cache_init(cache_dir);
cache_read();
#ifdef AUTH
host_reread(hosts_file);
#endif /* AUTH */
server_reread(servers_file);
if (was_enabled)
{
timer_start(curr_rate);
}
}
static void
handle_signals()
{
/* Ingore SIGPIPE since read will handle closed TCP connections.
Handle SIGHUP, SIGINT, and SIGCHLD. */
#ifdef HAVE_SIGSET
sigset(SIGPIPE, SIG_IGN);
sigset(SIGHUP, handle_sighup);
sigset(SIGINT, handle_sigint);
sigset(SIGCHLD, handle_sigchld);
#else
signal(SIGPIPE, SIG_IGN);
signal(SIGHUP, handle_sighup);
signal(SIGINT, handle_sigint);
signal(SIGCHLD, handle_sigchld);
#endif
}
static void
handle_sighup()
{
report(REPORT_DEBUG, "received SIGHUP signal\n");
need_reset();
}
static void
handle_sigint()
{
report(REPORT_DEBUG, "received SIGINT signal\n");
done(0);
}
static void
handle_sigchld()
{
pid_t pid;
SPOOL *sp;
#ifdef linux
handle_signals();
#endif
report(REPORT_DEBUG, "received SIGCHLD signal\n");
#ifdef HAVE_WAITPID
pid = waitpid((pid_t) - 1, NULL, WNOHANG);
#else /* not HAVE_WAITPID */
pid = wait(NULL);
#endif /* not HAVE_WAITPID */
if (pid <= 0)
{
return;
}
}
static void
audio_test()
{
static char *spin = "|\\-/";
static int spin_pos;
printf("Testing supported audio configurations (this may take a long time)...\n");
for (optional_sample_rate = 4000; optional_sample_rate <= 48000; optional_sample_rate += 5)
for (optional_precision = 8; optional_precision <= 16; optional_precision += 8)
for (optional_channels = 1; optional_channels <= 2; optional_channels++)
for (optional_format = 1; optional_format <= 5; optional_format++)
{
if (!spin[spin_pos])
{
spin_pos = 0;
}
fprintf(stderr, "%c\r", spin[spin_pos++]);
if (rplay_audio_init() >= 0)
{
printf(" { %5d, %-24s, %2d, %d },\n",
rplay_audio_sample_rate,
rplay_audio_format == RPLAY_FORMAT_ULAW ? "RPLAY_FORMAT_ULAW" :
rplay_audio_format == RPLAY_FORMAT_LINEAR_8 ? "RPLAY_FORMAT_LINEAR_8" :
rplay_audio_format == RPLAY_FORMAT_ULINEAR_8 ? "RPLAY_FORMAT_ULINEAR_8" :
rplay_audio_format == RPLAY_FORMAT_LINEAR_16 ? "RPLAY_FORMAT_LINEAR_16" :
rplay_audio_format == RPLAY_FORMAT_ULINEAR_16 ? "RPLAY_FORMAT_ULINEAR_16" : "?",
rplay_audio_precision,
rplay_audio_channels);
}
}
}
#ifdef __STDC__
void
done(int exit_value)
#else
void
done(exit_value)
int exit_value;
#endif
{
SPOOL *sp;
/* #ifdef HAVE_CDROM */
/* for (sp = spool; sp; sp = sp->next) */
/* { */
/* if (sp->si && sp->si->pid > 0) */
/* { */
/* report (REPORT_DEBUG, "killing process %d\n", sp->si->pid); */
/* if (kill (sp->si->pid, SIGKILL) < 0) */
/* { */
/* report (REPORT_DEBUG, "kill %d: %s\n", sp->si->pid, sys_err_str (errno)); */
/* } */
/* } */
/* } */
/* #endif */
/* connection_cleanup (); */
cache_cleanup();
rplay_audio_close();
close(rplay_fd);
close(rptp_fd);
#ifdef OTHER_RPLAY_PORTS
close(other_rplay_fd);
close(other_rptp_fd);
#endif
report(REPORT_DEBUG, "exit(%d)\n", exit_value);
if (logging && log_fd >= 0)
{
close(log_fd);
}
exit(exit_value);
}
#ifdef __STDC__
void
report(int level, char *fmt,...)
#else
void
report(va_alist)
va_dcl
#endif
{
va_list args;
char *p;
time_t now;
static struct iovec iov[2];
static char header[256];
static char message[2048]; /* XXX: largest message */
#ifdef __STDC__
va_start(args, fmt);
#else
char *fmt;
int level;
va_start(args);
level = va_arg(args, int);
fmt = va_arg(args, char *);
#endif
if (report_level >= level)
{
VSNPRINTF(SIZE(message, sizeof(message)), fmt, args);
iov[1].iov_base = message;
iov[1].iov_len = strlen(message);
if (logging)
{
now = time(0);
p = ctime(&now);
p += 4;
p[15] = '\0';
if (log_fd < 0)
{
#ifndef O_SYNC
#define O_SYNC 0
#endif
log_fd = open(log_file, O_WRONLY | O_CREAT | O_APPEND | O_SYNC, 0644);
if (log_fd < 0)
{
fprintf(stderr, "%s: cannot open logfile `%s': %s\n",
progname, log_file, sys_err_str(errno));
/*
* Turn off logging to avoid further problems.
*/
logging = 0;
log_fd = -1;
return;
}
}
SNPRINTF(SIZE(header, sizeof(header)), "%s %s rplayd[%d]: ", p, hostname, rplayd_pid);
iov[0].iov_base = header;
iov[0].iov_len = strlen(header);
if (writev(log_fd, iov, 2) < 0)
{
fprintf(stderr, "%s: cannot write to log file `%s': %s\n",
progname, log_file, sys_err_str(errno));
logging = 0;
log_fd = -1;
fprintf(stderr, "%s: logging disabled\n", progname);
}
}
if (debug || level == REPORT_ERROR)
{
SNPRINTF(SIZE(header, sizeof(header)), "%s: ", progname);
iov[0].iov_base = header;
iov[0].iov_len = strlen(header);
#ifndef STDERR_FILENO
#define STDERR_FILENO 2
#endif
writev(STDERR_FILENO, iov, 2);
}
}
va_end(args);
}
void
usage()
{
printf("\n");
printf("rplay %s\n", RPLAY_VERSION);
printf("\n");
printf("usage: %s [options]\n", progname);
printf("\n");
printf("-A DEVICE, --audio-device=DEVICE\n");
printf("\tUse DEVICE for the audio device (%s).\n", rplay_audio_device);
printf("\n");
#ifndef HAVE_OSS
printf("-b N, --audio-bufsize=N\n");
printf("\tAudio buffer size (%d).\n", rplay_audio_bufsize);
printf("\n");
#endif /* !HAVE_OSS */
printf("-B N, --audio-bits=N\n");
printf("\tAudio device bits per sample, 8 or 16.\n");
printf("\n");
printf("--audio-channels=N\n");
printf("\tNumber of audio channels to use, 1 == mono, 2 == stereo.\n");
printf("\n");
printf("-c N, --audio-close=N\n");
printf("\tClose %s after N idle seconds, disabled with 0 (%d).\n",
RPLAY_AUDIO_DEVICE, rplay_audio_timeout);
printf("\n");
printf("-F N, --audio_flush=N\n");
printf("\tFlush %s after N idle seconds, disabled with 0 (%d).\n",
RPLAY_AUDIO_DEVICE, rplay_audio_flush_timeout);
printf("\tN = -1 : flush when spool is empty.\n");
printf("\tN = -2 : flush after each audio write. (not recommended)\n");
printf("\tN should be <= to the audio close timeout.\n");
printf("\n");
printf("--audio-format=FORMAT\n");
printf("\tTell rplayd to write audio data using FORMAT, where FORMAT\n");
printf("\tcan be ulaw, linear-8, ulinear-8, linear-16, or ulinear-16.\n");
printf("\t(linear = signed, ulinear = unsigned)\n");
printf("\n");
#ifdef HAVE_OSS
printf("--audio-fragsize=N\n");
printf("\tAudio fragment size (%d). The default size is zero which lets\n",
rplay_audio_fragsize);
printf("\tthe audio driver pick the \"best\" size. The size specified must\n");
printf("\tbe a power of 2 greater than 16. Example: 256, 1024, 4096.\n");
printf("\n");
#endif /* HAVE_OSS */
printf("--audio-info=INFO, --info=INFO, -i INFO\n");
printf("\tSpecify complete audio device information with one option.\n");
printf("\tINFO is of the form: format,sample-rate,bits,channels\n");
printf("\tExamples: `ulaw,8000,8,1' and `linear-16,44100,16,2'\n");
printf("\tAlso provided are:\n");
printf("\t --audio-info-ulaw, --info-ulaw -> ulaw,8000,8,1\n");
printf("\n");
printf("--audio-match\n");
printf("\tAttempt to match the sample rate of the audio device with\n");
printf("\tthe sample rate of the current sound when no other sounds\n");
printf("\tare playing. If the match fails, --audio-sample-rate is used.\n");
printf("\tThis option overrides --audio-bufsize.\n");
printf("\n");
printf("--audio-port=PORT[,PORT...]\n");
printf("\tOutput audio to the specified audio port(s).\n");
printf("\tValid ports are `speaker', `headphone', and `lineout'.\n");
printf("\tMultiple ports can be specified using `speaker,headphone,lineout'\n");
printf("\n");
printf("-r N, --audio-rate=N\n");
printf("\tWrite the audio buffer N times per second (%d).\n", rplay_audio_rate);
printf("\n");
printf("-R N, --audio-sample-rate=N\n");
printf("\tSample rate of the audio device.\n");
printf("\n");
#ifdef AUTH
printf("--auth\n");
printf("\tEnable host access authentication.\n");
printf("\n");
#endif
printf("-D DIR, --cache-directory=DIR\n");
printf("\tUse DIR for rplay.cache (%s).\n", RPLAY_CACHE);
printf("\n");
printf("--cache-remove\n");
printf("\tRemove the cache directory and all its contents when rplayd exists.\n");
printf("\n");
printf("-s N, --cache-size=N\n");
printf("\tMaximum size in bytes of the rplay cache, disabled with 0 (%d).\n",
RPLAY_CACHE_SIZE);
printf("\n");
#ifdef HAVE_CDROM
printf("--cdrom0=DEVICE, --cdrom1=DEVICE, --cdrom2=DEVICE, --cdrom3=DEVICE\n");
printf("\tSpecify the cdrom[0-3] to DEVICE mapping. For Solaris 2.x the default\n");
printf("\tmapping is cdrom[0-3] -> /vol/dev/aliases/cdrom[0-3].\n");
printf("\tLinux uses cdrom[0-3] -> /dev/cdrom[0-3].\n");
printf("\n");
#endif /* HAVE_CDROM */
printf("-C FILE, --conf=FILE\n");
printf("\tUse FILE for rplay.conf (%s).\n", RPLAY_CONF);
printf("\n");
printf("-T N, --connection-timeout=N\n");
printf("\tClose idle RPTP connections after N seconds, disabled with 0 (%d).\n",
rptp_timeout);
printf("\n");
printf("-d, --debug\n");
printf("\tEnable debug mode.\n");
printf("\n");
printf("-f HOST, --forward=HOST\n");
printf("\tForward all RPLAY packets to HOST.\n");
printf("\n");
printf("--fork\n");
printf("\tEnable backgrounding rplayd at startup. (%s)\n",
do_fork ? "enabled" : "disabled");
printf("\n");
printf("--group=GROUP\n");
printf("\tRun with GROUP privs. (%s)\n", run_as_group);
printf("\n");
printf("--help\n");
printf("\tDisplay helpful information.\n");
printf("\n");
#ifdef HAVE_HELPERS
printf("--helpers=FILE\n");
printf("\tUse FILE for rplay.helpers (%s).\n", RPLAY_HELPERS);
printf("\n");
#endif /* HAVE_HELPERS */
#ifdef AUTH
printf("-H FILE, --hosts=FILE\n");
printf("\tUse FILE for rplay.hosts (%s).\n", RPLAY_HOSTS);
printf("\n");
#endif /* AUTH */
printf("--inetd\n");
printf("\tEnable inetd mode. (%s)\n", inetd ? "enabled" : "disabled");
printf("\n");
printf("-L FILE, --log-file=FILE\n");
printf("\tUse file for rplay.log (%s).\n", RPLAY_LOG);
printf("\n");
printf("-l N, --log-level=N\n");
printf("\tUse logging level N where %d <= n <= %d.\n", REPORT_MIN, REPORT_MAX);
printf("\n");
printf("--memory-cache-size=N\n");
printf("\tMaximum size in bytes of the memory cache, disable caching with 0 (%d).\n",
MEMORY_CACHE_SIZE);
printf("\n");
printf("--memory-cache-sound-size=N\n");
printf("\tMaximum size in bytes of a sound that can be cached in memory.\n");
printf("\tA value of 0 means to try and cache all sounds. (%d)\n",
MEMORY_CACHE_SOUND_SIZE);
printf("\n");
printf("-N, --no-audio\n");
printf("\tDisable audio, RPTP file server mode.\n");
printf("\n");
#ifdef AUTH
printf("--no-auth\n");
printf("\tDisable host access authentication.\n");
printf("\n");
#endif
printf("-n, --no-inetd\n");
printf("\tDisable inetd mode. (%s)\n", inetd ? "enabled" : "disabled");
printf("\n");
printf("--no-fork\n");
printf("\tDisable backgrounding rplayd at startup. (%s)\n",
do_fork ? "enabled" : "disabled");
printf("\n");
printf("--options-file=FILE\n");
printf("\tRead rplayd options from FILE.\n");
printf("\n");
printf("--port=PORT, --rplay-port=PORT\n");
printf("\tUse PORT as the RPLAY/UDP port. (%d)\n", RPLAY_PORT);
printf("\t(--other-rplay-port may also be available)\n");
printf("\n");
printf("--rptp-port=PORT\n");
printf("\tUse PORT as the RPTP/TCP port. (%d)\n", RPTP_PORT);
printf("\t(--other-rptp-port may also be available)\n");
printf("\n");
printf("-S FILE, --servers=FILE\n");
printf("\tUse FILE for rplay.servers (%s).\n", RPLAY_SERVERS);
printf("\n");
printf("-t N, --timeout=N\n");
printf("\tExit after N idle seconds, disabled with 0 (%d).\n", rplayd_timeout);
printf("\n");
printf("--user=USER\n");
printf("\tRun with USER privs. (%s)\n", run_as_user);
printf("\n");
printf("-v, --version\n");
printf("\tPrint the rplay version and exit.\n");
}