aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2016-01-04 21:11:38 +0100
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2016-01-04 21:11:38 +0100
commit7d222a52c70cc78e80ad0bab2f7f77dd03ad12b8 (patch)
treeccef3e4de7d28ceb88a707e6f118a84ba51318fb
parentef1e279e624f6139c83fa56604586dcf2b5571c8 (diff)
downloadapplause2-7d222a52c70cc78e80ad0bab2f7f77dd03ad12b8.tar.gz
added a simple server functionality and some SciTECO macros to interface it
* the server is basically a second way to modify the Lua state of Applause * concurrently to interactive input, messages can be sent over a TCP socket which are evaluated just like command lines. All stdout/stderr output is returned and the socket is closed. * Server currently hardcoded at 127.0.0.1:10000 * Interruptions are currently not possible. This would require another thread. * The threading could be simplified by making the applause binary a server-only application. The Read-Eval-Print loop could then be a standalone LuaJIT script.
-rw-r--r--applause.c250
-rw-r--r--client.tes19
2 files changed, 230 insertions, 39 deletions
diff --git a/applause.c b/applause.c
index 56fba8d..28210c9 100644
--- a/applause.c
+++ b/applause.c
@@ -1,5 +1,6 @@
#define _XOPEN_SOURCE
#define _POSIX_C_SOURCE 199506L
+#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
@@ -7,11 +8,15 @@
#include <signal.h>
#include <unistd.h>
#include <errno.h>
+#include <assert.h>
#include <sys/types.h>
+#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
+#include <arpa/inet.h>
+
#include <pthread.h>
#include <lua.h>
@@ -28,6 +33,9 @@
#define LUA_MODULE "applause.lua"
+#define CMD_SERVER_IP "127.0.0.1"
+#define CMD_SERVER_PORT 10000
+
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
static jack_port_t *output_port;
@@ -684,6 +692,186 @@ traceback(lua_State *L)
return 1;
}
+static void
+do_command(lua_State *L, const char *command)
+{
+ int stack_top;
+ char *buffer;
+
+ /* the error hanlder function for lua_pcall() */
+ lua_pushcfunction(L, traceback);
+
+ stack_top = lua_gettop(L);
+
+ /*
+ * AFAIK, we cannot support automatic printing of values
+ * returned by expressions.
+ * The chunk must return something (using `return`).
+ * We cannot always prepend a `return` since AFAIK it is
+ * impossible to embedded statements into expressions.
+ * Therefore we support "=" as a shortcut to "return" just
+ * like the luajit shell does.
+ */
+ if (*command == '=') {
+ if (asprintf(&buffer, "return %s", command+1) < 0)
+ buffer = NULL;
+ } else {
+ buffer = strdup(command);
+ }
+ assert(buffer != NULL);
+
+ if (luaL_loadstring(L, buffer) || lua_pcall(L, 0, LUA_MULTRET, -2)) {
+ fprintf(stderr, "Error.\n");
+ }
+
+ free(buffer);
+
+ /*
+ * Print values left on the stack:
+ * This includes error messages left by lua_pcall()
+ */
+ if (lua_gettop(L) > stack_top) {
+ lua_getglobal(L, "print");
+ lua_insert(L, stack_top + 1);
+
+ if (lua_pcall(L, lua_gettop(L) - stack_top - 1, 0, 0)) {
+ fprintf(stderr, "Error executing print().\n");
+ /* try to continue */
+ }
+ }
+
+ /* pop the traceback function */
+ lua_remove(L, -1);
+}
+
+/**
+ * Mutex protecting Lua state access after the command server
+ * has been launched.
+ */
+static pthread_mutex_t lua_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/*
+ * FIXME: This should perhaps be the only interface to the
+ * applause Lua state with the REPL loop implemented as a Lua
+ * script. This solution would simplify the C code and avoid
+ * threading. Also, then we could have multiple running
+ * REPL loops.
+ */
+static void *
+command_server(void *user_data)
+{
+ lua_State *L = user_data;
+ int socket_fd;
+ struct sockaddr_in server;
+
+ socket_fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (socket_fd < 0) {
+ perror("socket");
+ return NULL;
+ }
+
+ server.sin_addr.s_addr = inet_addr(CMD_SERVER_IP);
+ server.sin_family = AF_INET;
+ server.sin_port = htons(CMD_SERVER_PORT);
+
+ if (bind(socket_fd, (struct sockaddr *)&server,
+ sizeof(server)) < 0) {
+ perror("bind");
+ close(socket_fd);
+ return NULL;
+ }
+
+ if (listen(socket_fd, 1) < 0) {
+ perror("listen");
+ close(socket_fd);
+ return NULL;
+ }
+
+ for (;;) {
+ char length_buf[5 + 1];
+ size_t length;
+ char *command;
+
+ int old_stdout, old_stderr;
+
+ int client_fd = accept(socket_fd, NULL, NULL);
+
+ if (client_fd < 0) {
+ perror("accept");
+ break;
+ }
+
+ if (read(client_fd, length_buf, sizeof(length_buf)) <
+ sizeof(length_buf)) {
+ /* just close the connection and continue */
+ /*
+ * FIXME: We might want to handle errors
+ * (e.g. signal interruptions)
+ */
+ close(client_fd);
+ continue;
+ }
+
+ /* parse message length */
+ length = atoi(length_buf);
+ command = malloc(length);
+ assert(command != NULL);
+
+ if (read(client_fd, command, length) < length) {
+ /* just close the connection and continue */
+ /*
+ * FIXME: We might want to handle errors
+ * (e.g. signal interruptions)
+ */
+ free(command);
+ close(client_fd);
+ continue;
+ }
+
+ pthread_mutex_lock(&lua_mutex);
+
+ /*
+ * Redirect stdout and stderr to client_fd.
+ * This way, we capture all the output that the command
+ * may have.
+ */
+ old_stdout = dup(1);
+ close(1);
+ dup2(client_fd, 1);
+ old_stderr = dup(2);
+ close(2);
+ dup2(client_fd, 2);
+
+ /*
+ * FIXME: It would be nice to catch broken sockets
+ * (e.g. waiting for play() has been interrupted), in order
+ * to set the interrupted flag.
+ * However it seems we'd need another thread for that running
+ * do_command(), so we can select() the fd and eventually
+ * join the thread.
+ */
+ do_command(L, command);
+ free(command);
+
+ /*
+ * Restore stdout/stderr.
+ */
+ close(1);
+ dup2(old_stdout, 1);
+ close(old_stdout);
+ close(2);
+ dup2(old_stderr, 2);
+ close(old_stderr);
+
+ pthread_mutex_unlock(&lua_mutex);
+
+ close(client_fd);
+ }
+
+ close(socket_fd);
+ return NULL;
+}
+
typedef struct NativeMethod {
const char *class_name;
const char *method_name;
@@ -707,6 +895,8 @@ main(int argc, char **argv)
lua_State *L;
+ pthread_t command_server_thread;
+
/*
* FIXME: Support --help
*/
@@ -726,6 +916,9 @@ main(int argc, char **argv)
sigaction(SIGUSR1, &signal_action, NULL);
sigaction(SIGUSR2, &signal_action, NULL);
+ signal_action.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &signal_action, NULL);
+
L = luaL_newstate();
if (!L) {
fprintf(stderr, "Error creating Lua state.\n");
@@ -764,13 +957,19 @@ main(int argc, char **argv)
lua_setglobal(L, "samplerate");
/*
- * Push the error handler function. Will be used in the REPL
- * loop below.
+ * Launch the command server.
*/
- lua_pushcfunction(L, traceback);
+ if (pthread_create(&command_server_thread, NULL, command_server, L)) {
+ perror("pthread_create");
+ exit(EXIT_FAILURE);
+ }
+ /*
+ * Main REPL loop.
+ * Since the command server has been launched, all lua state access
+ * must be synchronized using lua_mutex.
+ */
for (;;) {
- int stack_top;
/*
* FIXME: Get global _PROMPT or _PROMPT2
*/
@@ -778,43 +977,12 @@ main(int argc, char **argv)
if (!line) {
putchar('\n');
- exit(EXIT_SUCCESS);
- }
-
- /*
- * Push print command
- */
- lua_getglobal(L, "print");
- stack_top = lua_gettop(L);
-
- /*
- * AFAIK, we cannot support automatic printing of values
- * returned by expressions.
- * The chunk must return something (using `return`).
- * We cannot always prepend a `return` since AFAIK it is
- * impossible to embedded statements into expressions.
- * Therefore we support "=" as a shortcut to "return" just
- * like the luajit shell does.
- */
- if (*line == '=') {
- char *buf = malloc(7 + strlen(line));
- sprintf(buf, "return %s", line+1);
- free(line);
- line = buf;
+ break;
}
- if (luaL_loadstring(L, line) || lua_pcall(L, 0, LUA_MULTRET, -3)) {
- fprintf(stderr, "Error.\n");
- }
-
- /*
- * Automatically print values left on the stack:
- * This includes error messages left by lua_pcall()
- */
- if (lua_pcall(L, lua_gettop(L) - stack_top, 0, 0)) {
- fprintf(stderr, "Error executing print().\n");
- /* try to continue */
- }
+ pthread_mutex_lock(&lua_mutex);
+ do_command(L, line);
+ pthread_mutex_unlock(&lua_mutex);
if (*line)
add_history(line);
@@ -822,6 +990,10 @@ main(int argc, char **argv)
free(line);
}
+ /*
+ * FIXME: Shut down connection server.
+ */
+
svsem_free(buffer_sem);
lua_close(L);
diff --git a/client.tes b/client.tes
new file mode 100644
index 0000000..77db8ab
--- /dev/null
+++ b/client.tes
@@ -0,0 +1,19 @@
+
+! Applause client: Evaluate command !
+@#ae{
+ X[applause.output]
+ EQ[applause.output]
+ Z\ 5-.<I > 10I
+ HECnc 127.0.0.1 10000 J
+ Z"= Q*U* '
+}
+
+! Print command !
+@#ap{
+ X[applause.output]
+ EQ[applause.output]
+ I= R
+ Z\ 5-.<I > 10I
+ HECnc 127.0.0.1 10000 J
+ Z"= Q*U* '
+}