Was cooking for almost one month and seems to work fine.
manual: Move /var/paraslash instructions to Troubleshooting.
Abstract sockets for server and audiod.
create_local_socket(): Avoid code duplication.
net.c: Combine remote_name() and __get_sock_name().
Remove socket address parameter of create_local_socket().
NEWS
====
+-----------------------------------------
+current master branch "magnetic momentum"
+-----------------------------------------
+ - 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.
+
+Download: ./releases/paraslash-git.tar.bz2 (tarball)
------------------------------------------
0.5.4 (2015-01-23) "exponential alignment"
------------------------------------------
{
int ret, socket_fd;
char *socket_name = conf.afs_socket_arg;
- struct sockaddr_un unix_addr;
unlink(socket_name);
- ret = create_local_socket(socket_name, &unix_addr,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH);
+ ret = create_local_socket(socket_name, 0);
if (ret < 0) {
- PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret), socket_name);
- exit(EXIT_FAILURE);
+ ret = create_local_socket(socket_name,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH);
+ if (ret < 0) {
+ PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret),
+ socket_name);
+ exit(EXIT_FAILURE);
+ }
}
socket_fd = ret;
- if (listen(socket_fd , 5) < 0) {
- PARA_EMERG_LOG("can not listen on socket\n");
- exit(EXIT_FAILURE);
- }
- ret = mark_fd_nonblocking(socket_fd);
- if (ret < 0) {
- close(socket_fd);
- return ret;
- }
PARA_INFO_LOG("listening on socket %s (fd %d)\n", socket_name,
socket_fd);
return socket_fd;
*/
static struct status_task *stat_task = &status_task_struct;
-/**
- * the task for handling audiod commands
+/*
+ * The task for handling audiod commands.
*
- * \sa struct task, struct sched
+ * We need two listening sockets for backward compability: on Linux systems
+ * fd[0] is an abstract socket (more precisely, a socket bound to an address in
+ * the abstract namespace), and fd[1] is the usual pathname socket. On other
+ * systems, fd[0] is negative, and only the pathname socket is used.
+ *
+ * For 0.5.x we accept connections on both sockets to make sure that old
+ * para_audioc versions can still connect. New versions use only the abstract
+ * socket. Hence after v0.6.0 we can go back to a single socket, either an
+ * abstract one (Linux) or a pathname socket (all other systems).
*/
struct command_task {
- /** the local listening socket */
- int fd;
+ /** The local listening sockets. */
+ int fd[2];
/** the associated task structure */
struct task *task;
};
}
/* does not unlink socket on errors */
-static int audiod_get_socket(void)
+static void init_local_sockets(struct command_task *ct)
{
- struct sockaddr_un unix_addr;
- int ret, fd;
-
if (conf.socket_given)
socket_name = para_strdup(conf.socket_arg);
else {
PARA_NOTICE_LOG("local socket: %s\n", socket_name);
if (conf.force_given)
unlink(socket_name);
- ret = create_local_socket(socket_name, &unix_addr,
+ ct->fd[0] = create_local_socket(socket_name, 0);
+ ct->fd[1] = create_local_socket(socket_name,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH);
- if (ret < 0)
- goto err;
- fd = ret;
- if (listen(fd , 5) < 0) {
- ret = -ERRNO_TO_PARA_ERROR(errno);
- goto err;
- }
- ret = mark_fd_nonblocking(fd);
- if (ret < 0)
- goto err;
- return fd;
-err:
- PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+ if (ct->fd[0] >= 0 || ct->fd[1] >= 0)
+ return;
+ PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd[1]));
exit(EXIT_FAILURE);
}
static void command_pre_select(struct sched *s, void *context)
{
struct command_task *ct = context;
- para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
+ int i;
+
+ for (i = 0; i < 2; i++)
+ if (ct->fd[i] >= 0)
+ para_fd_set(ct->fd[i], &s->rfds, &s->max_fileno);
}
static int command_post_select(struct sched *s, void *context)
{
- int ret;
+ int ret, i;
struct command_task *ct = context;
static struct timeval last_status_dump;
struct timeval tmp, delay;
- bool force = true;
+ bool force = false;
ret = task_get_notification(ct->task);
if (ret < 0)
return ret;
- ret = handle_connect(ct->fd, &s->rfds);
- if (ret < 0) {
- PARA_ERROR_LOG("%s\n", para_strerror(-ret));
- if (ret == -E_AUDIOD_TERM) {
- task_notify_all(s, -ret);
- return ret;
- }
- } else if (ret > 0)
+ for (i = 0; i < 2; i++) {
+ if (ct->fd[i] < 0)
+ continue;
+ ret = handle_connect(ct->fd[i], &s->rfds);
+ if (ret < 0) {
+ PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ if (ret == -E_AUDIOD_TERM) {
+ task_notify_all(s, -ret);
+ return ret;
+ }
+ } else if (ret > 0)
+ force = true;
+ }
+ if (force == true)
goto dump;
/* if last status dump was less than 500ms ago, do nothing */
delay.tv_sec = 5;
delay.tv_usec = 0;
tv_add(&last_status_dump, &delay, &tmp);
- if (tv_diff(now, &tmp, NULL) < 0)
- force = false;
+ if (tv_diff(now, &tmp, NULL) > 0)
+ force = true;
dump:
audiod_status_dump(force);
last_status_dump = *now;
static void init_command_task(struct command_task *ct)
{
- ct->fd = audiod_get_socket(); /* doesn't return on errors */
+ init_local_sockets(ct); /* doesn't return on errors */
ct->task = task_register(&(struct task_info) {
.name = "command",
}
/**
- * Look up the local or remote side of a connected socket structure.
+ * Look up the remote side of a connected socket structure.
*
* \param fd The socket descriptor of the connected socket.
- * \param getname Either \p getsockname() for local, or \p getpeername() for
- * remote side.
*
* \return A static character string identifying hostname and port of the
* chosen side in numeric host:port format.
* \sa getsockname(2), getpeername(2), parse_url(), getnameinfo(3),
* services(5), nsswitch.conf(5).
*/
-static char *__get_sock_name(int fd, typeof(getsockname) getname)
+char *remote_name(int fd)
{
struct sockaddr_storage ss;
const struct sockaddr *sa;
static char output[sizeof(hbuf) + sizeof(sbuf) + 4];
int ret;
- if (getname(fd, (struct sockaddr *)&ss, &sslen) < 0) {
+ if (getpeername(fd, (struct sockaddr *)&ss, &sslen) < 0) {
PARA_ERROR_LOG("can not determine address from fd %d: %s\n",
fd, strerror(errno));
snprintf(output, sizeof(output), "(unknown)");
return output;
}
-/**
- * Look up the remote side of a connected socket structure.
- *
- * \param sockfd The file descriptor of the socket.
- *
- * \return A pointer to a static buffer containing hostname an port. This
- * buffer must not be freed by the caller.
- */
-char *remote_name(int sockfd)
-{
- return __get_sock_name(sockfd, getpeername);
-}
-
/**
* Extract IPv4 or IPv6-mapped-IPv4 address from sockaddr_storage.
*
* \return Positive on success, \p -E_NAME_TOO_LONG if \a name is longer
* than \p UNIX_PATH_MAX.
*/
-static int init_unix_addr(struct sockaddr_un *u, const char *name)
+static int init_unix_addr(struct sockaddr_un *u, const char *name,
+ bool abstract)
{
- if (strlen(name) >= UNIX_PATH_MAX)
+ if (strlen(name) + abstract >= UNIX_PATH_MAX)
return -E_NAME_TOO_LONG;
memset(u->sun_path, 0, UNIX_PATH_MAX);
u->sun_family = PF_UNIX;
- strcpy(u->sun_path, name);
+ strcpy(u->sun_path + abstract, name);
return 1;
}
/**
- * Prepare, create, and bind a socket for local communication.
+ * Create a socket for local communication and listen on it.
*
* \param name The socket pathname.
- * \param unix_addr Pointer to the \p AF_UNIX socket structure.
- * \param mode The desired mode of the socket.
+ * \param mode The desired permissions of the socket.
+ *
+ * This function creates a passive local socket for sequenced, reliable,
+ * two-way, connection-based byte streams. The socket file descriptor is set to
+ * nonblocking mode and listen(2) is called to prepare the socket for
+ * accepting incoming connection requests.
*
- * This function creates a local socket for sequenced, reliable,
- * two-way, connection-based byte streams.
+ * If mode is zero, an abstract socket (a non-portable Linux extension) is
+ * created. In this case the socket name has no connection with filesystem
+ * pathnames.
*
- * \return The file descriptor, on success, negative on errors.
+ * \return The file descriptor on success, negative error code on failure.
*
- * \sa socket(2)
- * \sa bind(2)
- * \sa chmod(2)
+ * \sa socket(2), \sa bind(2), \sa chmod(2), listen(2), unix(7).
*/
-int create_local_socket(const char *name, struct sockaddr_un *unix_addr,
- mode_t mode)
+int create_local_socket(const char *name, mode_t mode)
{
+ struct sockaddr_un unix_addr;
int fd, ret;
+ bool abstract = mode == 0;
- ret = init_unix_addr(unix_addr, name);
+ ret = init_unix_addr(&unix_addr, name, abstract);
if (ret < 0)
return ret;
ret = socket(PF_UNIX, SOCK_STREAM, 0);
if (ret < 0)
return -ERRNO_TO_PARA_ERROR(errno);
fd = ret;
- ret = bind(fd, (struct sockaddr *) unix_addr, UNIX_PATH_MAX);
+ ret = mark_fd_nonblocking(fd);
+ if (ret < 0)
+ goto err;
+ ret = bind(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr));
if (ret < 0) {
ret = -ERRNO_TO_PARA_ERROR(errno);
goto err;
}
- ret = -E_CHMOD;
- if (chmod(name, mode) < 0)
+ if (!abstract) {
+ ret = -E_CHMOD;
+ if (chmod(name, mode) < 0)
+ goto err;
+ }
+ if (listen(fd , 5) < 0) {
+ ret = -ERRNO_TO_PARA_ERROR(errno);
goto err;
+ }
return fd;
err:
close(fd);
int fd, ret;
PARA_DEBUG_LOG("connecting to %s\n", name);
- ret = init_unix_addr(&unix_addr, name);
- if (ret < 0)
- return ret;
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return -ERRNO_TO_PARA_ERROR(errno);
- if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) == -1) {
- ret = -ERRNO_TO_PARA_ERROR(errno);
+ /* first try (linux-only) abstract socket */
+ ret = init_unix_addr(&unix_addr, name, true);
+ if (ret < 0)
goto err;
- }
- return fd;
+ if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) != -1)
+ return fd;
+ /* next try pathname socket */
+ ret = init_unix_addr(&unix_addr, name, false);
+ if (ret < 0)
+ goto err;
+ if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) != -1)
+ return fd;
+ ret = -ERRNO_TO_PARA_ERROR(errno);
err:
close(fd);
return ret;
int recv_buffer(int fd, char *buf, size_t size);
int para_accept(int fd, fd_set *rfds, void *addr, socklen_t size, int *new_fd);
-int create_local_socket(const char *name, struct sockaddr_un *unix_addr,
- mode_t mode);
+int create_local_socket(const char *name, mode_t mode);
int connect_local_socket(const char *name);
int recv_cred_buffer(int, char *, size_t);
ssize_t send_cred_buffer(int, char*);
*Step 2*: Start para_server
-Before starting the server make sure you have write permissions to
-the directory /var/paraslash that has been created during installation:
-
- sudo chown $LOGNAME /var/paraslash
-
-Alternatively, use the --afs-socket Option to specify a different
-location for the AFS command socket.
-
For this first try, we'll use the info loglevel to make the output
of para_server more verbose.
*Step 4*: Configure para_audiod
-para_audiod needs to create a "well-known" socket for the clients to
-connect to. The default path for this socket is
-
- /var/paraslash/audiod_socket.$HOSTNAME
-
-In order to make this directory writable for para_audiod, execute
-as bar@client_host
-
- sudo chown $LOGNAME /var/paraslash
-
-
-We will also have to tell para_audiod that it should receive the
-audio stream from server_host via http:
+We will have to tell para_audiod that it should receive the audio
+stream from server_host via http:
para_audiod -l info -r '.:http -i server_host'
*Troubleshooting*
-It did not work? To find out why, try to receive, decode and play the
-stream manually using para_recv, para_filter and para_write as follows.
+If you receive a socket related error on server or audiod startup,
+make sure you have write permissions to the /var/paraslash directory:
+
+ sudo chown $LOGNAME /var/paraslash
+Alternatively, use the --afs-socket (para_server) or --socket
+(para_audiod) option to specify a different socket pathname.
+
+To identify streaming problems try to receive, decode and play the
+stream manually using para_recv, para_filter and para_write as follows.
For simplicity we assume that you're running Linux/ALSA and that only
MP3 files have been added to the database.
connectionless transport, which is the reason that it is currently
only available via UDP.
+Abstract socket namespace
+~~~~~~~~~~~~~~~~~~~~~~~~~
+UNIX domain sockets are a traditional way to communicate between
+processes on the same machine. They are always reliable (see above)
+and don't reorder datagrams. Unlike TCP and UDP, UNIX domain sockets
+support passing open file descriptors or process credentials to
+other processes.
+
+The usual way to set up a UNIX domain socket (as obtained from
+socket(2)) for listening is to first bind the socket to a file system
+pathname and then call listen(2), then accept(2). Such sockets are
+called _pathname sockets_ because bind(2) creates a special socket
+file at the specified path. Pathname sockets allow unrelated processes
+to communicate with the listening process by binding to the same path
+and calling connect(2).
+
+There are two problems with pathname sockets:
+
+ * The listing process must be able to (safely) create the
+ socket special in a directory which is also accessible to
+ the connecting process.
+
+ * After an unclean shutdown of the listening process, a stale
+ socket special may reside on the file system.
+
+The abstract socket namespace is a non-portable Linux feature which
+avoids these problems. Abstract sockets are still bound to a name,
+but the name has no connection with file system pathnames.
+
License
~~~~~~~