#include <ctype.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include "command.h"
#include "ipc.h"
#include "log.h"
#include "misc.h"
#include "status.h"
#define RECV_BUF_SIZE 32
#define LISTEN_BACKLOG 2
static Ipc_ctx * ipc_ctx_init(const char *udsock_path)
{
Ipc_ctx *ret = xmalloc(sizeof(Ipc_ctx));
pthread_mutex_init(&ret->track_lock, NULL);
pthread_mutex_lock(&ret->track_lock);
pthread_mutex_init(&ret->pause_lock, NULL);
pthread_cond_init(&ret->pause_cond, NULL);
if ((ret->udsockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
LOG_DIE("socket: %s", strerror(errno));
fcntl(ret->udsockfd, F_SETFD, FD_CLOEXEC);
struct sockaddr_un addr = {AF_UNIX, {0}};
const size_t sockpathlen = strlen(udsock_path);
if (sockpathlen > SUN_PATH_LEN - 1)
LOG_DIE("Socket path is too long, max length %zu", SUN_PATH_LEN - 1);
memcpy(addr.sun_path, udsock_path, sockpathlen + 1);
if (bind(ret->udsockfd, (struct sockaddr *) &addr,
sizeof(struct sockaddr_un)) == -1)
LOG_DIE("bind to %s: %s", udsock_path, strerror(errno));
ret->udsock_path = udsock_path;
ret->action = ACTION_NONE;
return ret;
}
static void ipc_ctx_free(Ipc_ctx *ctx)
{
pthread_mutex_destroy(&ctx->track_lock);
pthread_mutex_destroy(&ctx->pause_lock);
pthread_cond_destroy(&ctx->pause_cond);
xclose(ctx->udsockfd);
(void)unlink(ctx->udsock_path);
}
static void resume_main_thread(Ipc_ctx *ctx)
{
pthread_mutex_lock(&ctx->pause_lock);
pthread_mutex_unlock(&ctx->pause_lock);
pthread_cond_signal(&ctx->pause_cond);
}
static Action cmd_to_action[] =
{
ACTION_PLAY,
ACTION_PAUSE,
-1,
ACTION_QUIT,
ACTION_TRACK_NEXT,
ACTION_TRACK_PREV,
ACTION_TRACK_REWIND,
ACTION_TRACK_FIRST,
-1
};
static void close_stream(void *arg)
{
xfclose(arg);
}
static void * ipc_routine(void *arg)
{
Ipc_ctx *ctx = arg;
bool loop = true;
if (listen(ctx->udsockfd, LISTEN_BACKLOG) == -1)
LOG_DIE("listen: %s", strerror(errno));
while (loop)
{
int conn_sock = accept(ctx->udsockfd, NULL, NULL);
if (conn_sock == -1) /* TODO: handle errors by importance */
continue;
FILE *conn_stream = fdopen(conn_sock, "w");
if (conn_stream == NULL)
{
log_append(LOG_ERR, "fdopen: %s", strerror(errno));
xclose(conn_sock);
continue;
}
pthread_cleanup_push(close_stream, conn_stream);
static char recv_buf[RECV_BUF_SIZE];
ssize_t rcvd = recv_full(conn_sock, recv_buf, sizeof(recv_buf));
shutdown(conn_sock, SHUT_RD);
if (rcvd == -1)
LOG_DIE("recv: %s", strerror(errno));
if (rcvd == 0)
{
log_append(LOG_ERR, "recv: connection closed by peer");
goto CLEAN;
}
if (rcvd == sizeof(recv_buf) + 1)
{
fprintf(conn_stream, "Message got truncated (max length: %zu)\n",
sizeof(recv_buf));
--rcvd;
}
/* Trim ending whitespace */
for (char *p = recv_buf + rcvd - 1; p != recv_buf && isspace(*p); --p)
--rcvd;
log_append(LOG_INFO, "Received command %.*s", (int)rcvd, recv_buf);
const Gperf_match *match = cmd_match(recv_buf, rcvd);
if (match == NULL)
{
log_append(LOG_ERR, "%.*s: unknown command", (int)rcvd, recv_buf);
fprintf(conn_stream, "%.*s: unknown command", (int)rcvd, recv_buf);
goto CLEAN;
}
(void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_mutex_lock(&ctx->track_lock);
switch (match->id)
{
case CMD_STATUS: ;
status_fprint(conn_stream, *ctx->pstate, ctx->tinfo);
break;
case CMD_TOGGLE_PLAY_PAUSE:
if (*ctx->pstate == STATE_PAUSED)
{
ctx->action = ACTION_PLAY;
resume_main_thread(ctx);
}
else if (*ctx->pstate == STATE_PLAYING)
ctx->action = ACTION_PAUSE;
break;
case CMD_QUIT:
ctx->action = ACTION_QUIT;
if (*ctx->pstate == STATE_PAUSED)
resume_main_thread(ctx);
loop = false;
break;
default:
ctx->action = cmd_to_action[match->id];
if (*ctx->pstate == STATE_PAUSED)
resume_main_thread(ctx);
}
pthread_mutex_unlock(&ctx->track_lock);
(void)pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if (match->id != CMD_STATUS)
fprintf(conn_stream, "OK");
CLEAN:
pthread_cleanup_pop(1);
}
return NULL;
}
Ipc_ctx * ipc_thread_spawn(const char *udsock_path)
{
Ipc_ctx *ret = ipc_ctx_init(udsock_path);
int err = pthread_create(&ret->tid, NULL, ipc_routine, ret);
if (err)
{
log_append(LOG_ERR, "IPC thread creation: %s", strerror(err));
ipc_ctx_free(ret);
return NULL;
}
return ret;
}
void ipc_thread_join(Ipc_ctx *ctx)
{
pthread_cancel(ctx->tid);
(void)shutdown(ctx->udsockfd, SHUT_RDWR); /* Make accept return EINVAL */
pthread_join(ctx->tid, NULL);
ipc_ctx_free(ctx);
}