diff options
author | Laurence Withers <lwithers@amethyst.(none)> | 2009-07-22 19:46:43 +0000 |
---|---|---|
committer | Laurence Withers <lwithers@amethyst.(none)> | 2009-07-22 19:46:43 +0000 |
commit | 353472953ada54d5e1b5d235ca472254478d17ad (patch) | |
tree | a761128a993525d30acc5950fb133e31e3302aff | |
parent | e0a3e5e4ff2b2d038df52936c5ccd4b3b40e198d (diff) |
Add talk_to_socket handler
This handler talks directly to a daemon using a Unix SOCK_SEQPACKET socket,
allowing e.g. cookie handlers to be implemented without having to fork and
and execute an external program or script interpreter.
A little explanation of the functioning of talk_to_socket():
1. We receive our argument as a string in the format of "HANDLER EXTRA_ARGS".
2. Copy "HANDLER" into our Unix socket address structure and connect to that
address (blocking). Error out if connection fails.
3. Write "EXTRA_ARGS" to the socket. Error out if writing fails.
4. Use poll() to wait for a response, timing out after 500ms of inactivity.
5. Use ioctl(FIONREAD) to find out how long the pending datagram is.
6. Allocate an appropriately sized buffer (len+1) and write a null at the
end, so our output is always null-terminated no matter what.
7. Read from socket into buffer. Error out if read fails.
8. Close socket.
-rw-r--r-- | uzbl.c | 114 | ||||
-rw-r--r-- | uzbl.h | 3 |
2 files changed, 116 insertions, 1 deletions
@@ -54,6 +54,8 @@ #include <errno.h> #include <fcntl.h> #include <signal.h> +#include <poll.h> +#include <sys/ioctl.h> #include "uzbl.h" #include "config.h" @@ -808,6 +810,7 @@ struct {char *key; CommandInfo value;} cmdlist[] = { "sync_spawn", {spawn_sync, 0} }, // needed for cookie handler { "sh", {spawn_sh, 0} }, { "sync_sh", {spawn_sh_sync, 0} }, // needed for cookie handler + { "talk_to_socket", {talk_to_socket, TRUE} }, { "exit", {close_uzbl, 0} }, { "search", {search_forward_text, TRUE} }, { "search_reverse", {search_reverse_text, TRUE} }, @@ -1394,6 +1397,114 @@ spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result) { } void +talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + + int fd, len; + struct sockaddr_un sa; + char* sockpath, * cmd; + ssize_t cmd_len, ret; + struct pollfd pfd; + + if(uzbl.comm.sync_stdout) uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + + /* This function could be optimised by storing a hash table of socket paths + and associated connected file descriptors rather than closing and + re-opening for every call. Also we could launch a script if socket connect + fails. */ + + /* Test arguments passed in correctly: argv should be an array of one element, + which should be a string consisting of the socket path followed by a space + followed by the command to write to the socket. Check that the socket path + is valid (starts with '/' and fits in a sockaddr_un.sun_path[]). */ + if(argv->len != 1) { + g_printerr("talk_to_socket called with argv->len %d\n", (int)(argv->len)); + return; + } + + sockpath = g_array_index(argv, char*, 0); + cmd = strchr(sockpath, ' '); + if(!cmd || *sockpath != '/' || (cmd - sockpath) >= (int)sizeof(sa.sun_path)) { + g_printerr("talk_to_socket called incorrectly (%s)\n", sockpath); + return; + } + + /* copy socket path, null terminate result */ + memcpy(sa.sun_path, sockpath, (cmd - sockpath)); + sa.sun_path[cmd - sockpath] = 0; + sa.sun_family = AF_UNIX; + + /* point to start of command, find its length in bytes */ + ++cmd; + cmd_len = strlen(cmd); + + /* create socket file descriptor and connect it to path */ + fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if(fd == -1) { + g_printerr("talk_to_socket: creating socket failed (%s)\n", strerror(errno)); + return; + } + if(connect(fd, (struct sockaddr*)&sa, sizeof(sa))) { + g_printerr("talk_to_socket: connect failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* write request */ + ret = write(fd, cmd, cmd_len); + if(ret == -1) { + g_printerr("talk_to_socket: write failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* wait for a response, with a 500ms timeout */ + pfd.fd = fd; + pfd.events = POLLIN; + while(1) { + ret = poll(&pfd, 1, 500); + if(ret == 1) break; + if(ret == 0) errno = ETIMEDOUT; + if(errno == EINTR) continue; + g_printerr("talk_to_socket: poll failed while waiting for input (%s)\n", + strerror(errno)); + close(fd); + return; + } + + /* get length of response */ + if(ioctl(fd, FIONREAD, &len) == -1) { + g_printerr("talk_to_socket: cannot find daemon response length, " + "ioctl failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* if there is a response, read it */ + if(len) { + uzbl.comm.sync_stdout = g_malloc(len + 1); + if(!uzbl.comm.sync_stdout) { + g_printerr("talk_to_socket: failed to allocate %d bytes\n", len); + close(fd); + return; + } + uzbl.comm.sync_stdout[len] = 0; /* ensure result is null terminated */ + + ret = read(fd, uzbl.comm.sync_stdout, len); + if(ret == -1) { + g_printerr("talk_to_socket: failed to read from socket (%s)\n", + strerror(errno)); + close(fd); + return; + } + } + + /* clean up */ + close(fd); + return; +} + +void parse_command(const char *cmd, const char *param, GString *result) { CommandInfo *c; @@ -2251,7 +2362,8 @@ inject_handler_args(const gchar *actname, const gchar *origargs, const gchar *ne if ((g_strcmp0(actname, "spawn") == 0) || (g_strcmp0(actname, "sh") == 0) || (g_strcmp0(actname, "sync_spawn") == 0) || - (g_strcmp0(actname, "sync_sh") == 0)) { + (g_strcmp0(actname, "sync_sh") == 0) || + (g_strcmp0(actname, "talk_to_socket") == 0)) { guint i; GString *a = g_string_new(""); gchar **spawnparts = split_quoted(origargs, FALSE); @@ -338,6 +338,9 @@ char* build_progressbar_ascii(int percent); void +talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result); + +void spawn(WebKitWebView *web_view, GArray *argv, GString *result); void |