char *socket_name = conf.afs_socket_arg;
unlink(socket_name);
- ret = create_local_socket(socket_name,
- 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;
PARA_INFO_LOG("listening on socket %s (fd %d)\n", socket_name,
*/
static struct status_task *stat_task = &status_task_struct;
-/**
- * the task for handling audiod commands
+/*
+ * The task for handling audiod commands.
+ *
+ * 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.
*
- * \sa struct task, struct sched
+ * 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)
{
- int ret;
-
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,
+ 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;
- return ret;
-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",
* \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;
}
* nonblocking mode and listen(2) is called to prepare the socket for
* accepting incoming connection requests.
*
+ * 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 error code on failure.
*
* \sa socket(2), \sa bind(2), \sa chmod(2), listen(2), unix(7).
{
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);
ret = mark_fd_nonblocking(fd);
if (ret < 0)
goto err;
- ret = bind(fd, (struct sockaddr *)&unix_addr, UNIX_PATH_MAX);
+ 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)
- goto err;
+ 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;
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;
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
~~~~~~~