ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

ltkd.c (31307B)


      1 /* FIXME: Figure out how to properly print window id */
      2 /* FIXME: error checking in tokenizer (is this necessary?) */
      3 /* FIXME: strip whitespace at end of lines in socket format */
      4 /*
      5  * Copyright (c) 2016-2024 lumidify <nobody@lumidify.org>
      6  *
      7  * Permission to use, copy, modify, and/or distribute this software for any
      8  * purpose with or without fee is hereby granted, provided that the above
      9  * copyright notice and this permission notice appear in all copies.
     10  *
     11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     18  */
     19 
     20 #include <time.h>
     21 #include <stdio.h>
     22 #include <fcntl.h>
     23 #include <errno.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 #include <stdarg.h>
     27 #include <unistd.h>
     28 #include <signal.h>
     29 #include <locale.h>
     30 #include <inttypes.h>
     31 
     32 #include <sys/un.h>
     33 #include <sys/select.h>
     34 #include <sys/socket.h>
     35 
     36 #include "err.h"
     37 #include "ltkd.h"
     38 #include "util.h"
     39 #include "widget.h"
     40 #include "cmd_helpers.h"
     41 #include "proto_types.h"
     42 
     43 #include <ltk/memory.h>
     44 #include <ltk/ltk.h>
     45 #include <ltk/util.h>
     46 #include <ltk/text.h>
     47 #include <ltk/macros.h>
     48 #include <ltk/graphics.h>
     49 
     50 #define MAX_SOCK_CONNS 20
     51 #define READ_BLK_SIZE 128
     52 #define WRITE_BLK_SIZE 128
     53 
     54 struct token_list {
     55 	ltkd_cmd_token *tokens;
     56 	/* FIXME: size_t everywhere */
     57 	int num_tokens;
     58 	int num_alloc;
     59 };
     60 
     61 /* FIXME: switch to size_t */
     62 static struct ltkd_sock_info {
     63 	int fd;                    /* file descriptor for socket connection */
     64 	int event_mask;            /* events to send to socket */
     65 	char *read;                /* text read from socket */
     66 	int read_len;              /* length of text in read buffer */
     67 	int read_alloc;            /* size of read buffer */
     68 	char *to_write;            /* text to be written to socket */
     69 	int write_len;             /* length of text in write buffer */
     70 	int write_cur;             /* length of text already written */
     71 	int write_alloc;           /* size of write buffer */
     72 	/* stuff for tokenizing */
     73 	int in_token;              /* last read char is inside token */
     74 	int offset;                /* offset from removing backslashes */
     75 	int in_str;                /* last read char is inside string */
     76 	int read_cur;              /* length of text already tokenized */
     77 	int bs;                    /* last char was non-escaped backslash */
     78 	struct token_list tokens;  /* current tokens */
     79 	uint32_t last_seq;         /* sequence number of last request processed */
     80 } sockets[MAX_SOCK_CONNS];
     81 
     82 static int daemonize_flag = 1;
     83 
     84 static void ltkd_mainloop(ltk_window *window);
     85 static char *get_sock_path(char *basedir, unsigned long id);
     86 static FILE *open_log(char *dir);
     87 static void daemonize(void);
     88 static int read_sock(struct ltkd_sock_info *sock);
     89 static int push_token(struct token_list *tl, char *token);
     90 static int read_sock(struct ltkd_sock_info *sock);
     91 static int write_sock(struct ltkd_sock_info *sock);
     92 static int tokenize_command(struct ltkd_sock_info *sock);
     93 static int ltkd_set_root_widget_cmd(ltk_window *window, ltkd_cmd_token *tokens, int num_tokens, ltkd_error *err);
     94 static int process_commands(ltk_window *window, int client);
     95 static int add_client(int fd);
     96 static int listen_sock(const char *sock_path);
     97 static int accept_sock(int listenfd);
     98 static void ltkd_quit(void);
     99 static void ltkd_cleanup(void);
    100 
    101 static short maxsocket = -1;
    102 static short running = 1;
    103 static short sock_write_available = 0;
    104 static int base_dir_fd = -1;
    105 static char *ltkd_dir = NULL;
    106 static FILE *ltkd_logfile = NULL;
    107 static char *sock_path = NULL;
    108 /* Note: Most functions still take this explicitly because it wasn't
    109    global originally, but that's just the way it is. */
    110 static ltk_window *main_window = NULL;
    111 
    112 typedef struct {
    113 	char *name;
    114 	int (*cmd)(ltk_window *, ltkd_cmd_token *, size_t, ltkd_error *);
    115 } ltkd_widget_funcs;
    116 
    117 /* FIXME: use binary search when searching for the widget */
    118 ltkd_widget_funcs widget_funcs[] = {
    119 	{
    120 		.name = "box",
    121 		.cmd = &ltkd_box_cmd
    122 	},
    123 	{
    124 		.name = "button",
    125 		.cmd = &ltkd_button_cmd
    126 	},
    127 	{
    128 		.name = "entry",
    129 		.cmd = &ltkd_entry_cmd
    130 	},
    131 	{
    132 		.name = "grid",
    133 		.cmd = &ltkd_grid_cmd
    134 	},
    135 	{
    136 		.name = "label",
    137 		.cmd = &ltkd_label_cmd
    138 	},
    139 	{
    140 		.name = "image",
    141 		.cmd = &ltkd_image_widget_cmd
    142 	},
    143 	{
    144 		.name = "menu",
    145 		.cmd = &ltkd_menu_cmd
    146 	},
    147 	{
    148 		.name = "menuentry",
    149 		.cmd = &ltkd_menuentry_cmd
    150 	},
    151 	{
    152 		.name = "submenu",
    153 		.cmd = &ltkd_menu_cmd
    154 	},
    155 };
    156 
    157 static int
    158 ltkd_window_close(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) {
    159 	(void)self;
    160 	(void)args;
    161 	(void)data;
    162 	ltkd_quit();
    163 	return 1;
    164 }
    165 
    166 int
    167 main(int argc, char *argv[]) {
    168 	setlocale(LC_CTYPE, "");
    169 	/* FIXME: decide where to add this (currently called in ltk, but kind of weird) */
    170 	/*XSetLocaleModifiers("");*/
    171 	int ch;
    172 	char *title = "LTK Window";
    173 	while ((ch = getopt(argc, argv, "dt:")) != -1) {
    174 		switch (ch) {
    175 			case 't':
    176 				title = optarg;
    177 				break;
    178 			case 'd':
    179 				daemonize_flag = 0;
    180 				break;
    181 			default:
    182 				ltkd_fatal("USAGE: ltkd [-t title]\n");
    183 		}
    184 	}
    185 
    186 	ltkd_dir = ltk_setup_directory("LTKDDIR", ".ltkd", 1);
    187 	if (!ltkd_dir) ltkd_fatal_errno("Unable to setup ltkd directory.\n");
    188 	/* FIXME: this is only used to use unlinkat for deleting the socket in
    189 	   the end in case ltkd_dir is relative. It would probably be better
    190 	   to just use getcwd to turn that into an absolute path instead.
    191 	   Or maybe ltkd should just stay in the current directory instead of
    192 	   moving to / in daemonize(). */
    193 	base_dir_fd = open(".", O_RDONLY);
    194 	if (base_dir_fd < 0)
    195 		ltkd_fatal_errno("Unable to open current directory.\n");
    196 	ltkd_logfile = open_log(ltkd_dir);
    197 	if (!ltkd_logfile) ltkd_fatal_errno("Unable to open log file.\n");
    198 
    199 	ltk_init();
    200 	ltkd_widgets_init();
    201 
    202 	/* FIXME: set window size properly - I only run it in a tiling WM
    203 	   anyways, so it doesn't matter, but still... */
    204 	main_window = ltk_window_create(title, 0, 0, 500, 500);
    205 	ltk_widget_register_signal_handler(LTK_CAST_WIDGET(main_window), LTK_WINDOW_SIGNAL_CLOSE, &ltkd_window_close, LTK_ARG_VOID);
    206 
    207 	sock_path = get_sock_path(ltkd_dir, ltk_renderer_get_window_id(main_window->renderwindow));
    208 	if (!sock_path) ltkd_fatal_errno("Unable to allocate memory for socket path.\n");
    209 
    210 	/* Note: sockets should be initialized to 0 because it is static */
    211 	for (int i = 0; i < MAX_SOCK_CONNS; i++) {
    212 		sockets[i].fd = -1; /* socket unused */
    213 		/* initialize these just because I'm paranoid */
    214 		sockets[i].read = NULL;
    215 		sockets[i].to_write = NULL;
    216 		sockets[i].tokens.tokens = NULL;
    217 	}
    218 
    219 	ltkd_mainloop(main_window);
    220 	return 0;
    221 }
    222 
    223 /* FIXME: need to recalculate maxfd when removing client */
    224 static struct {
    225 	fd_set rallfds, wallfds;
    226 	int maxfd;
    227 	int listenfd;
    228 } sock_state;
    229 
    230 /* FIXME: this is extremely dangerous right now because pretty much any command
    231    can be executed, so for instance the widget that caused the lock could also
    232    be destroyed, causing issues when this function returns */
    233 int
    234 ltkd_handle_lock_client(ltk_window *window, int client) {
    235 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
    236 		return 0;
    237 	fd_set rfds, wfds, rallfds, wallfds;
    238 	int clifd = sockets[client].fd;
    239 	FD_ZERO(&rallfds);
    240 	FD_ZERO(&wallfds);
    241 	FD_SET(clifd, &rallfds);
    242 	FD_SET(clifd, &wallfds);
    243 	int retval;
    244 	struct timeval tv;
    245 	tv.tv_sec = 0;
    246 	tv.tv_usec = 0;
    247 	struct timespec now, elapsed, last, sleep_time;
    248 	clock_gettime(CLOCK_MONOTONIC, &last);
    249 	sleep_time.tv_sec = 0;
    250 	while (1) {
    251 		rfds = rallfds;
    252 		wfds = wallfds;
    253 		retval = select(clifd + 1, &rfds, &wfds, NULL, &tv);
    254 
    255 		if (retval > 0) {
    256 			if (FD_ISSET(clifd, &rfds)) {
    257 				int ret;
    258 				while ((ret = read_sock(&sockets[client])) == 1) {
    259 					int pret;
    260 					if ((pret = process_commands(window, client)) == 1)
    261 						return 1;
    262 					else if (pret == -1)
    263 						return 0;
    264 				}
    265 				/* FIXME: maybe also return on read error? or would that be dangerous? */
    266 				if (ret == 0) {
    267 					FD_CLR(clifd, &sock_state.rallfds);
    268 					FD_CLR(clifd, &sock_state.wallfds);
    269 					ltkd_widget_remove_client(client);
    270 					sockets[clifd].fd = -1;
    271 					close(clifd);
    272 					int newmaxsocket = -1;
    273 					for (int j = 0; j <= maxsocket; j++) {
    274 						if (sockets[j].fd >= 0)
    275 							newmaxsocket = j;
    276 					}
    277 					maxsocket = newmaxsocket;
    278 					if (maxsocket == -1) {
    279 						ltkd_quit();
    280 						break;
    281 					}
    282 					return 0;
    283 				}
    284 			}
    285 			if (FD_ISSET(clifd, &wfds)) {
    286 				/* FIXME: call in loop like above */
    287 				write_sock(&sockets[client]);
    288 			}
    289 		}
    290 		clock_gettime(CLOCK_MONOTONIC, &now);
    291 		ltk_timespecsub(&now, &last, &elapsed);
    292 		/* FIXME: configure framerate */
    293 		if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) {
    294 			sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec;
    295 			nanosleep(&sleep_time, NULL);
    296 		}
    297 		last = now;
    298 	}
    299 	return 0;
    300 }
    301 
    302 /* FIXME: need to remove event masks from all widgets when removing client */
    303 static void
    304 ltkd_mainloop(ltk_window *window) {
    305 	fd_set rfds, wfds;
    306 	int retval;
    307 	int clifd;
    308 	struct timeval tv;
    309 	tv.tv_sec = 0;
    310 	tv.tv_usec = 0;
    311 
    312 	FD_ZERO(&sock_state.rallfds);
    313 	FD_ZERO(&sock_state.wallfds);
    314 
    315 	if ((sock_state.listenfd = listen_sock(sock_path)) < 0)
    316 		ltkd_fatal_errno("Error listening on socket.\n");
    317 
    318 	FD_SET(sock_state.listenfd, &sock_state.rallfds);
    319 	sock_state.maxfd = sock_state.listenfd;
    320 
    321 	printf("%lu", ltk_renderer_get_window_id(main_window->renderwindow));
    322 	fflush(stdout);
    323 	if (daemonize_flag)
    324 		daemonize();
    325 
    326 	ltk_mainloop_init();
    327 
    328 	while (running) {
    329 		rfds = sock_state.rallfds;
    330 		wfds = sock_state.wallfds;
    331 		retval = select(sock_state.maxfd + 1, &rfds, &wfds, NULL, &tv);
    332 		if (retval > 0) {
    333 			if (FD_ISSET(sock_state.listenfd, &rfds)) {
    334 				if ((clifd = accept_sock(sock_state.listenfd)) < 0) {
    335 					/* FIXME: Just log this! */
    336 					ltkd_fatal_errno("Error accepting socket connection.\n");
    337 				}
    338 				int i = add_client(clifd);
    339 				FD_SET(clifd, &sock_state.rallfds);
    340 				FD_SET(clifd, &sock_state.wallfds);
    341 				if (clifd > sock_state.maxfd)
    342 					sock_state.maxfd = clifd;
    343 				if (i > maxsocket)
    344 					maxsocket = i;
    345 				continue;
    346 			}
    347 			for (int i = 0; i <= maxsocket; i++) {
    348 				if ((clifd = sockets[i].fd) < 0)
    349 					continue;
    350 				if (FD_ISSET(clifd, &rfds)) {
    351 					/* FIXME: better error handling - this assumes error
    352 					   is always because read would block */
    353 					/* FIXME: maybe maximum number of iterations here to
    354 					   avoid choking on a lot of data? although such a
    355 					   large amount of data would probably cause other
    356 					   problems anyways */
    357 					/* or maybe measure time and break after max time? */
    358 					int ret;
    359 					while ((ret = read_sock(&sockets[i])) == 1) {
    360 						process_commands(window, i);
    361 					}
    362 					if (ret == 0) {
    363 						ltkd_widget_remove_client(i);
    364 						FD_CLR(clifd, &sock_state.rallfds);
    365 						FD_CLR(clifd, &sock_state.wallfds);
    366 						sockets[i].fd = -1;
    367 						/* FIXME: what to do on error? */
    368 						close(clifd);
    369 						int newmaxsocket = -1;
    370 						for (int j = 0; j <= maxsocket; j++) {
    371 							if (sockets[j].fd >= 0)
    372 								newmaxsocket = j;
    373 						}
    374 						maxsocket = newmaxsocket;
    375 						if (maxsocket == -1) {
    376 							ltkd_quit();
    377 							break;
    378 						}
    379 					}
    380 				}
    381 				/* FIXME: maybe ignore SIGPIPE signal - then don't call FD_CLR
    382 				   for wallfds above but rather when write fails with EPIPE */
    383 				/* -> this would possibly allow data to be written still in the
    384 				   hypothetical scenario that only the writing end of the socket
    385 				   is closed (and ltkd wouldn't crash if only the reading end is
    386 				   closed) */
    387 				if (FD_ISSET(clifd, &wfds)) {
    388 					/* FIXME: also call in loop like reading above */
    389 					write_sock(&sockets[i]);
    390 				}
    391 			}
    392 		}
    393 
    394 		ltk_mainloop_step(1);
    395 	}
    396 
    397 	ltkd_cleanup();
    398 }
    399 
    400 /* largely copied from APUE */
    401 static void
    402 daemonize(void) {
    403 	pid_t pid;
    404 	struct sigaction sa;
    405 
    406 	fflush(stdout);
    407 	fflush(stderr);
    408 	fflush(ltkd_logfile);
    409 
    410 	if ((pid = fork()) < 0)
    411 		ltkd_fatal_errno("Can't fork.\n");
    412 	else if (pid != 0)
    413 		exit(0);
    414 	setsid();
    415 
    416 	sa.sa_handler = SIG_IGN;
    417 	sigemptyset(&sa.sa_mask);
    418 	sa.sa_flags = 0;
    419 	if (sigaction(SIGHUP, &sa, NULL) < 0)
    420 		ltkd_fatal_errno("Unable to ignore SIGHUP.\n");
    421 	if ((pid  = fork()) < 0)
    422 		ltkd_fatal_errno("Can't fork.\n");
    423 	else if (pid != 0)
    424 		exit(0);
    425 
    426 	/* FIXME: does this cause any issues with the lazy initialization of pango/fontconfig? */
    427 	if (chdir("/") < 0)
    428 		ltkd_fatal_errno("Can't change directory to root.\n");
    429 
    430 	/* FIXME: error handling */
    431 	int devnull = open("/dev/null", O_RDONLY);
    432 	if (devnull >= 0)
    433 		dup2(devnull, fileno(stdin));
    434 	dup2(fileno(ltkd_logfile), fileno(stdout));
    435 	dup2(fileno(ltkd_logfile), fileno(stderr));
    436 }
    437 
    438 static char *
    439 get_sock_path(char *basedir, unsigned long id) {
    440 	int len;
    441 	char *path;
    442 
    443 	len = strlen(basedir);
    444 	/* FIXME: MAKE SURE THIS IS ACTUALLY BIG ENOUGH! */
    445 	path = ltk_malloc(len + 20);
    446 	/* FIXME: also check for less than 0 */
    447 	if (snprintf(path, len + 20, "%s/%lu.sock", basedir, id) >= len + 20)
    448 		ltkd_fatal("Tell lumidify to fix his code.\n");
    449 
    450 	return path;
    451 }
    452 
    453 static FILE *
    454 open_log(char *dir) {
    455 	FILE *f;
    456 	char *path;
    457 
    458 	path = ltk_strcat_useful(dir, "/ltkd.log");
    459 	if (!path)
    460 		return NULL;
    461 	f = fopen(path, "a");
    462 	if (!f) {
    463 		ltk_free(path);
    464 		return NULL;
    465 	}
    466 	ltk_free(path);
    467 
    468 	return f;
    469 }
    470 
    471 static void
    472 ltkd_cleanup(void) {
    473 	if (sock_path) {
    474 		/* FIXME: somewhat misleading warning message */
    475 		if (base_dir_fd >= 0)
    476 			unlinkat(base_dir_fd, sock_path, 0);
    477 		else
    478 			ltk_warn("Unable to remove socket file!\n");
    479 		ltk_free(sock_path);
    480 	}
    481 	if (base_dir_fd >= 0)
    482 		close(base_dir_fd);
    483 	if (sock_state.listenfd >= 0)
    484 		close(sock_state.listenfd);
    485 	if (ltkd_dir)
    486 		ltk_free(ltkd_dir);
    487 	if (ltkd_logfile)
    488 		fclose(ltkd_logfile);
    489 
    490 	for (int i = 0; i < MAX_SOCK_CONNS; i++) {
    491 		if (sockets[i].fd >= 0)
    492 			close(sockets[i].fd);
    493 		if (sockets[i].read)
    494 			ltk_free(sockets[i].read);
    495 		if (sockets[i].to_write)
    496 			ltk_free(sockets[i].to_write);
    497 		if (sockets[i].tokens.tokens)
    498 			ltk_free(sockets[i].tokens.tokens);
    499 	}
    500 
    501 	ltkd_widgets_cleanup();
    502 	main_window = NULL;
    503 	ltk_deinit();
    504 }
    505 
    506 static void
    507 ltkd_quit(void) {
    508 	running = 0;
    509 }
    510 
    511 static void
    512 ltkd_log_msg(const char *mode, const char *format, va_list args) {
    513 	char logtime[25]; /* FIXME: This should always be big enough, right? */
    514 	time_t clock;
    515 	struct tm *timeptr;
    516 
    517 	time(&clock);
    518 	timeptr = localtime(&clock);
    519 	strftime(logtime, 25, "%Y-%m-%d %H:%M:%S", timeptr);
    520 
    521 	if (main_window)
    522 		fprintf(stderr, "%s ltkd(%lu) %s: ", logtime, ltk_renderer_get_window_id(main_window->renderwindow), mode);
    523 	else
    524 		fprintf(stderr, "%s ltkd(?) %s: ", logtime, mode);
    525 	vfprintf(stderr, format, args);
    526 }
    527 
    528 static int
    529 ltkd_set_root_widget_cmd(
    530     ltk_window *window,
    531     ltkd_cmd_token *tokens,
    532     int num_tokens,
    533     ltkd_error *err) {
    534 	ltkd_widget *widget;
    535 	if (num_tokens != 2) {
    536 		err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
    537 		err->arg = -1;
    538 		return 1;
    539 	} else if (tokens[1].contains_nul) {
    540 		err->type = ERR_INVALID_ARGUMENT;
    541 		err->arg = 1;
    542 		return 1;
    543 	}
    544 	widget = ltkd_get_widget(tokens[1].text, LTK_WIDGET_UNKNOWN, err);
    545 	if (!widget) {
    546 		err->arg = 1;
    547 		return 1;
    548 	}
    549 	ltk_window_set_root_widget(window, widget->widget);
    550 
    551 	return 0;
    552 }
    553 
    554 /* Push a token onto `token_list`, resizing the buffer if necessary.
    555    Returns -1 on error, 0 otherwise.
    556    Note: The token is not copied, it is only added directly. */
    557 static int
    558 push_token(struct token_list *tl, char *token) {
    559 	int new_size;
    560 	if (tl->num_tokens >= tl->num_alloc) {
    561 		new_size = (tl->num_alloc * 2) > (tl->num_tokens + 1) ?
    562 			   (tl->num_alloc * 2) : (tl->num_tokens + 1);
    563 		ltkd_cmd_token *new = ltk_reallocarray(tl->tokens, new_size, sizeof(ltkd_cmd_token));
    564 		if (!new) return -1;
    565 		tl->tokens = new;
    566 		tl->num_alloc = new_size;
    567 	}
    568 	tl->tokens[tl->num_tokens].text = token;
    569 	tl->tokens[tl->num_tokens].len = 0;
    570 	tl->tokens[tl->num_tokens++].contains_nul = 0;
    571 
    572 	return 0;
    573 }
    574 
    575 /* Add a new client to the socket list and return the index in `sockets`.
    576    Returns -1 if there is no space for a new client. */
    577 static int
    578 add_client(int fd) {
    579 	for (int i = 0; i < MAX_SOCK_CONNS; i++) {
    580 		if (sockets[i].fd == -1) {
    581 			sockets[i].fd = fd;
    582 			sockets[i].event_mask = ~0; /* FIXME */
    583 			sockets[i].read_len = 0;
    584 			sockets[i].write_len = 0;
    585 			sockets[i].write_cur = 0;
    586 			sockets[i].offset = 0;
    587 			sockets[i].in_str = 0;
    588 			sockets[i].read_cur = 0;
    589 			sockets[i].bs = 0;
    590 			sockets[i].tokens.num_tokens = 0;
    591 			sockets[i].last_seq = 0;
    592 			return i;
    593 		}
    594 	}
    595 
    596 	return -1;
    597 }
    598 
    599 /* largely copied from APUE */
    600 /* Listen on the socket at `sock_path`.
    601    Returns the file descriptor of the opened socket on success.
    602    Returns -1 if `sock_path` is too long
    603            -2 if the socket could not be created
    604            -3 if the socket could not be bound to the path
    605            -4 if the socket could not be listened on */
    606 static int
    607 listen_sock(const char *sock_path) {
    608 	int fd, len, err, rval;
    609 	struct sockaddr_un un;
    610 
    611 	if (strlen(sock_path) >= sizeof(un.sun_path)) {
    612 		errno = ENAMETOOLONG;
    613 		return -1;
    614 	}
    615 
    616 	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    617 		return -2;
    618 
    619 	unlink(sock_path);
    620 
    621 	memset(&un, 0, sizeof(un));
    622 	un.sun_family = AF_UNIX;
    623 	strcpy(un.sun_path, sock_path);
    624 	len = offsetof(struct sockaddr_un, sun_path) + strlen(sock_path);
    625 	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
    626 		rval = -3;
    627 		goto errout;
    628 	}
    629 
    630 	if (listen(fd, 10) < 0) {
    631 		rval = -4;
    632 		goto errout;
    633 	}
    634 
    635 	return fd;
    636 
    637 errout:
    638 	err = errno;
    639 	close(fd);
    640 	errno = err;
    641 	return rval;
    642 }
    643 
    644 /* Accept a socket connection on the listening socket `listenfd`.
    645    Returns the file descriptor of the accepted client on success.
    646    Returns -1 if there was an error. */
    647 static int
    648 accept_sock(int listenfd) {
    649 	int clifd;
    650 	socklen_t len;
    651 	struct sockaddr_un un;
    652 
    653 	len = sizeof(un);
    654 	if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
    655 		return -1;
    656 	}
    657 	if (ltkd_set_nonblock(clifd)) {
    658 		/* FIXME: what could even be done if close fails? */
    659 		close(clifd);
    660 		return -1;
    661 	}
    662 
    663 	return clifd;
    664 }
    665 
    666 /* Read up to READ_BLK_SIZE bytes from the socket `sock`.
    667    Returns -1 if an error occurred, 0 if the connection was closed, 1 otherwise.
    668    Note: Returning 1 on success is weird, but it could also be confusing to
    669    return 0 on success when `read` returns that to mean that the connection
    670    was closed. */
    671 static int
    672 read_sock(struct ltkd_sock_info *sock) {
    673 	int nread;
    674 	char *old = sock->read;
    675 	ltk_grow_string(&sock->read, &sock->read_alloc, sock->read_len + READ_BLK_SIZE);
    676 	/* move tokens to new addresses - this was added as an
    677 	   afterthought and really needs to be cleaned up */
    678 	if (sock->read != old) {
    679 		for (int i = 0; i < sock->tokens.num_tokens; i++) {
    680 			sock->tokens.tokens[i].text = sock->read + (sock->tokens.tokens[i].text - old);
    681 		}
    682 	}
    683 	nread = read(sock->fd, sock->read + sock->read_len, READ_BLK_SIZE);
    684 	if (nread == -1 || nread == 0)
    685 		return nread;
    686 	sock->read_len += nread;
    687 
    688 	return 1;
    689 }
    690 
    691 /* Write up to WRITE_BLK_SIZE bytes to the socket.
    692    Returns -1 on error, 0 otherwise. */
    693 static int
    694 write_sock(struct ltkd_sock_info *sock) {
    695 	if (sock->write_len == sock->write_cur)
    696 		return 0;
    697 	int write_len = WRITE_BLK_SIZE > sock->write_len - sock->write_cur ?
    698 			sock->write_len - sock->write_cur : WRITE_BLK_SIZE;
    699 	int nwritten = write(sock->fd, sock->to_write + sock->write_cur, write_len);
    700 	if (nwritten == -1)
    701 		return nwritten;
    702 	sock->write_cur += nwritten;
    703 
    704 	/* check if any sockets have text to write */
    705 	if (sock->write_cur == sock->write_len) {
    706 		int found = 0;
    707 		for (int i = 0; i < maxsocket; i++) {
    708 			if (sockets[i].fd != -1 &&
    709 			    sockets[i].write_cur != sockets[i].write_len) {
    710 				found = 1;
    711 				break;
    712 			}
    713 		}
    714 		if (!found)
    715 			sock_write_available = 0;
    716 	}
    717 
    718 	return 0;
    719 }
    720 
    721 static void
    722 move_write_pos(struct ltkd_sock_info *sock) {
    723 	/* FIXME: also resize if too large */
    724 	if (sock->write_cur > 0) {
    725 		memmove(sock->to_write, sock->to_write + sock->write_cur,
    726 		        sock->write_len - sock->write_cur);
    727 		sock->write_len -= sock->write_cur;
    728 		sock->write_cur = 0;
    729 	}
    730 }
    731 
    732 /* Queue `str` to be written to the socket. If len is < 0, it is set to `strlen(str)`.
    733    Returns -1 on error, 0 otherwise.
    734    Note: The string must include all '\n', etc. as defined in the protocol. This
    735    function just adds the given string verbatim. */
    736 int
    737 ltkd_queue_sock_write(int client, const char *str, int len) {
    738 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
    739 		return 1;
    740 	/* this is always large enough to hold a uint32_t and " \0" */
    741 	char num[12];
    742 	struct ltkd_sock_info *sock = &sockets[client];
    743 	move_write_pos(sock);
    744 	if (len < 0)
    745 		len = strlen(str);
    746 
    747 	int numlen = snprintf(num, sizeof(num), "%"PRIu32" ", sock->last_seq);
    748 	if (numlen < 0 || (unsigned)numlen >= sizeof(num))
    749 		ltkd_fatal("There's a bug in the universe.\n");
    750 	if (sock->write_alloc - sock->write_len < len + numlen)
    751 		ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + numlen);
    752 
    753 	(void)strncpy(sock->to_write + sock->write_len, num, numlen);
    754 	(void)strncpy(sock->to_write + sock->write_len + numlen, str, len);
    755 	sock->write_len += len + numlen;
    756 
    757 	sock_write_available = 1;
    758 
    759 	return 0;
    760 }
    761 
    762 int
    763 ltkd_queue_sock_write_fmt(int client, const char *fmt, ...) {
    764 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
    765 		return 1;
    766 	struct ltkd_sock_info *sock = &sockets[client];
    767 	/* just to print the sequence number */
    768 	ltkd_queue_sock_write(client, "", 0);
    769 	va_list args;
    770 	va_start(args, fmt);
    771 	int len = vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
    772 	if (len < 0) {
    773 		ltkd_fatal("Unable to print formatted text to socket.\n");
    774 	} else if (len >= sock->write_alloc - sock->write_len) {
    775 		va_end(args);
    776 		va_start(args, fmt);
    777 		/* snprintf always writes '\0', even though we don't actually need it here */
    778 		ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + 1);
    779 		vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
    780 	}
    781 	va_end(args);
    782 	sock->write_len += len;
    783 	sock_write_available = 1;
    784 
    785 	return 0;
    786 }
    787 
    788 /* Tokenize the current read buffer in `sock`.
    789    Returns 0 immediately if the end of a command was encountered, 1 otherwise. */
    790 static int
    791 tokenize_command(struct ltkd_sock_info *sock) {
    792 	for (; sock->read_cur < sock->read_len; sock->read_cur++) {
    793 		/* FIXME: strip extra whitespace? Or maybe just have a "hard" protocol where it always has to be one space. */
    794 		if (!sock->in_token) {
    795 			push_token(&sock->tokens, sock->read + sock->read_cur - sock->offset);
    796 			sock->in_token = 1;
    797 		}
    798 		if (sock->read[sock->read_cur] == '\\') {
    799 			sock->bs++;
    800 			if (sock->bs / 2)
    801 				sock->offset++;
    802 			else
    803 				sock->tokens.tokens[sock->tokens.num_tokens - 1].len++;
    804 			sock->bs %= 2;
    805 			sock->read[sock->read_cur-sock->offset] = '\\';
    806 		} else if (sock->read[sock->read_cur] == '\n' && !sock->in_str) {
    807 			sock->read[sock->read_cur-sock->offset] = '\0';
    808 			sock->read_cur++;
    809 			sock->offset = 0;
    810 			sock->in_token = 0;
    811 			sock->bs = 0;
    812 			return 0;
    813 		} else if (sock->read[sock->read_cur] == '"') {
    814 			sock->offset++;
    815 			if (sock->bs) {
    816 				sock->read[sock->read_cur-sock->offset] = '"';
    817 				sock->bs = 0;
    818 			} else {
    819 				sock->in_str = !sock->in_str;
    820 			}
    821 		} else if (sock->read[sock->read_cur] == ' ' && !sock->in_str) {
    822 			sock->read[sock->read_cur-sock->offset] = '\0';
    823 			sock->in_token = !sock->in_token;
    824 			sock->bs = 0;
    825 		} else {
    826 			sock->read[sock->read_cur-sock->offset] = sock->read[sock->read_cur];
    827 			/* FIXME: assert that num_tokens > 0 */
    828 			sock->tokens.tokens[sock->tokens.num_tokens - 1].len++;
    829 			if (sock->read[sock->read_cur] == '\0')
    830 				sock->tokens.tokens[sock->tokens.num_tokens - 1].contains_nul = 1;
    831 			sock->bs = 0;
    832 		}
    833 	}
    834 
    835 	return 1;
    836 }
    837 
    838 /* FIXME: currently no type-checking when setting specific widget mask */
    839 /* FIXME: this is really ugly and inefficient right now - it will be replaced with something
    840    more generic at some point (or maybe just with a binary protocol?) */
    841 static int
    842 handle_mask_command(int client, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) {
    843 	if (num_tokens != 4 && num_tokens != 5) {
    844 		err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
    845 		err->arg = -1;
    846 		return 1;
    847 	}
    848 	/* FIXME: make this nicer */
    849 	/* -> use generic cmd handling like the widgets */
    850 	if (tokens[1].contains_nul) {
    851 		err->type = ERR_INVALID_ARGUMENT;
    852 		err->arg = 1;
    853 		return 1;
    854 	} else if (tokens[2].contains_nul) {
    855 		err->type = ERR_INVALID_ARGUMENT;
    856 		err->arg = 2;
    857 		return 1;
    858 	} else if (tokens[3].contains_nul) {
    859 		err->type = ERR_INVALID_ARGUMENT;
    860 		err->arg = 3;
    861 		return 1;
    862 	} else if (num_tokens == 5 && tokens[4].contains_nul) {
    863 		err->type = ERR_INVALID_ARGUMENT;
    864 		err->arg = 4;
    865 		return 1;
    866 	}
    867 	uint32_t mask = 0;
    868 	int lock = 0;
    869 	int special = 0;
    870 	ltkd_widget *widget = ltkd_get_widget(tokens[1].text, LTK_WIDGET_UNKNOWN, err);
    871 	if (!widget) {
    872 		err->arg = 1;
    873 		return 1;
    874 	}
    875 	if (!strcmp(tokens[2].text, "widget")) {
    876 		if (!strcmp(tokens[3].text, "mousepress")) {
    877 			mask = LTKD_PEVENTMASK_MOUSEPRESS;
    878 		} else if (!strcmp(tokens[3].text, "mouserelease")) {
    879 			mask = LTKD_PEVENTMASK_MOUSERELEASE;
    880 		} else if (!strcmp(tokens[3].text, "mousemotion")) {
    881 			mask = LTKD_PEVENTMASK_MOUSEMOTION;
    882 		} else if (!strcmp(tokens[3].text, "resize")) {
    883 			mask = LTKD_PEVENTMASK_RESIZE;
    884 		} else if (!strcmp(tokens[3].text, "statechange")) {
    885 			mask = LTKD_PEVENTMASK_STATECHANGE;
    886 		} else if (!strcmp(tokens[3].text, "none")) {
    887 			mask = LTKD_PEVENTMASK_NONE;
    888 		} else {
    889 			err->type = ERR_INVALID_ARGUMENT;
    890 			err->arg = 3;
    891 			return 1;
    892 		}
    893 	} else if (!strcmp(tokens[2].text, "menuentry")) {
    894 		if (!strcmp(tokens[3].text, "press")) {
    895 			mask = LTKD_PWEVENTMASK_MENUENTRY_PRESS;
    896 		} else if (!strcmp(tokens[3].text, "none")) {
    897 			mask = LTKD_PWEVENTMASK_MENUENTRY_NONE;
    898 		} else {
    899 			err->type = ERR_INVALID_ARGUMENT;
    900 			err->arg = 3;
    901 			return 1;
    902 		}
    903 		special = 1;
    904 	} else if (!strcmp(tokens[2].text, "button")) {
    905 		if (!strcmp(tokens[3].text, "press")) {
    906 			mask = LTKD_PWEVENTMASK_BUTTON_PRESS;
    907 		} else if (!strcmp(tokens[3].text, "none")) {
    908 			mask = LTKD_PWEVENTMASK_BUTTON_NONE;
    909 		} else {
    910 			err->type = ERR_INVALID_ARGUMENT;
    911 			err->arg = 3;
    912 			return 1;
    913 		}
    914 		special = 1;
    915 	} else {
    916 		err->type = ERR_INVALID_ARGUMENT;
    917 		err->arg = 2;
    918 		return 1;
    919 	}
    920 	if (num_tokens == 5) {
    921 		if (!strcmp(tokens[4].text, "lock")) {
    922 			lock = 1;
    923 		} else {
    924 			err->type = ERR_INVALID_ARGUMENT;
    925 			err->arg = 4;
    926 			return 1;
    927 		}
    928 	}
    929 
    930 	if (!strcmp(tokens[0].text, "mask-add")) {
    931 		if (lock) {
    932 			if (special)
    933 				ltkd_widget_add_to_event_lwmask(widget, client, mask);
    934 			else
    935 				ltkd_widget_add_to_event_lmask(widget, client, mask);
    936 		} else {
    937 			if (special)
    938 				ltkd_widget_add_to_event_wmask(widget, client, mask);
    939 			else
    940 				ltkd_widget_add_to_event_mask(widget, client, mask);
    941 		}
    942 	} else if (!strcmp(tokens[0].text, "mask-set")) {
    943 		if (lock) {
    944 			if (special)
    945 				ltkd_widget_set_event_lwmask(widget, client, mask);
    946 			else
    947 				ltkd_widget_set_event_lmask(widget, client, mask);
    948 		} else {
    949 			if (special)
    950 				ltkd_widget_set_event_wmask(widget, client, mask);
    951 			else
    952 				ltkd_widget_set_event_mask(widget, client, mask);
    953 		}
    954 	} else if (!strcmp(tokens[0].text, "mask-remove")) {
    955 		if (lock) {
    956 			if (special)
    957 				ltkd_widget_remove_from_event_lwmask(widget, client, mask);
    958 			else
    959 				ltkd_widget_remove_from_event_lmask(widget, client, mask);
    960 		} else {
    961 			if (special)
    962 				ltkd_widget_remove_from_event_wmask(widget, client, mask);
    963 			else
    964 				ltkd_widget_remove_from_event_mask(widget, client, mask);
    965 		}
    966 	} else {
    967 		err->type = ERR_INVALID_COMMAND;
    968 		err->arg = 0;
    969 		return 1;
    970 	}
    971 	return 0;
    972 }
    973 
    974 /* Process the commands as they are read from the socket. */
    975 /* Returns 1 if command was 'event-unlock true',
    976    -1 if command was 'event-unlock false', 0 otherwise. */
    977 static int
    978 process_commands(ltk_window *window, int client) {
    979 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
    980 		return 0;
    981 	struct ltkd_sock_info *sock = &sockets[client];
    982 	ltkd_cmd_token *tokens;
    983 	int num_tokens;
    984 	ltkd_error errdetail = {ERR_NONE, -1};
    985 	int err;
    986 	int retval = 0;
    987 	int last = 0;
    988 	uint32_t seq;
    989 	const char *errstr;
    990 	int contains_nul = 0;
    991 	while (!tokenize_command(sock)) {
    992 		contains_nul = 0;
    993 		err = 0;
    994 		tokens = sock->tokens.tokens;
    995 		num_tokens = sock->tokens.num_tokens;
    996 		if (num_tokens < 2) {
    997 			errdetail.type = ERR_INVALID_COMMAND;
    998 			errdetail.arg = -1;
    999 			err = 1;
   1000 		} else {
   1001 			contains_nul = tokens[0].contains_nul;
   1002 			seq = (uint32_t)ltk_strtonum(tokens[0].text, 0, UINT32_MAX, &errstr);
   1003 			tokens++;
   1004 			num_tokens--;
   1005 			if (errstr || contains_nul) {
   1006 				errdetail.type = ERR_INVALID_SEQNUM;
   1007 				errdetail.arg = -1;
   1008 				err = 1;
   1009 				seq = sock->last_seq;
   1010 			} else if (tokens[0].contains_nul) {
   1011 				errdetail.type = ERR_INVALID_ARGUMENT;
   1012 				errdetail.arg = 0;
   1013 				err = 1;
   1014 				seq = sock->last_seq;
   1015 			} else if (strcmp(tokens[0].text, "set-root-widget") == 0) {
   1016 				err = ltkd_set_root_widget_cmd(window, tokens, num_tokens, &errdetail);
   1017 			} else if (strcmp(tokens[0].text, "quit") == 0) {
   1018 				ltkd_quit();
   1019 				last = 1;
   1020 			} else if (strcmp(tokens[0].text, "destroy") == 0) {
   1021 				err = ltkd_widget_destroy_cmd(window, tokens, num_tokens, &errdetail);
   1022 			} else if (strncmp(tokens[0].text, "mask", 4) == 0) {
   1023 				err = handle_mask_command(client, tokens, num_tokens, &errdetail);
   1024 			} else if (strcmp(tokens[0].text, "event-unlock") == 0) {
   1025 				if (num_tokens != 2) {
   1026 					errdetail.type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
   1027 					errdetail.arg = -1;
   1028 					err = 1;
   1029 				} else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "true") == 0) {
   1030 					retval = 1;
   1031 				} else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "false") == 0) {
   1032 					retval = -1;
   1033 				} else {
   1034 					err = 1;
   1035 					errdetail.type = ERR_INVALID_ARGUMENT;
   1036 					errdetail.arg = 1;
   1037 				}
   1038 				last = 1;
   1039 			} else {
   1040 				int found = 0;
   1041 				for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
   1042 					if (widget_funcs[i].cmd && !strcmp(tokens[0].text, widget_funcs[i].name)) {
   1043 						err = widget_funcs[i].cmd(window, tokens, num_tokens, &errdetail);
   1044 						found = 1;
   1045 					}
   1046 				}
   1047 				if (!found) {
   1048 					errdetail.type = ERR_INVALID_COMMAND;
   1049 					errdetail.arg = -1;
   1050 					err = 1;
   1051 				}
   1052 			}
   1053 			sock->tokens.num_tokens = 0;
   1054 			sock->last_seq = seq;
   1055 		}
   1056 		if (err) {
   1057 			const char *errmsg = errtype_to_string(errdetail.type);
   1058 			if (ltkd_queue_sock_write_fmt(client, "err %d %d \"%s\"\n", errdetail.type, errdetail.arg, errmsg))
   1059 				ltkd_fatal("Unable to queue socket write.\n");
   1060 		} else {
   1061 			if (ltkd_queue_sock_write(client, "res ok\n", -1)) {
   1062 				ltkd_fatal("Unable to queue socket write.\n");
   1063 			}
   1064 		}
   1065 		if (last)
   1066 			break;
   1067 	}
   1068 	if (sock->tokens.num_tokens > 0 && sock->tokens.tokens[0].text != sock->read) {
   1069 		memmove(sock->read, sock->tokens.tokens[0].text, sock->read + sock->read_len - sock->tokens.tokens[0].text);
   1070 		ptrdiff_t offset = sock->tokens.tokens[0].text - sock->read;
   1071 		/* Hmm, seems a bit ugly... */
   1072 		for (int i = 0; i < sock->tokens.num_tokens; i++) {
   1073 			sock->tokens.tokens[i].text -= offset;
   1074 		}
   1075 		sock->read_len -= offset;
   1076 		sock->read_cur -= offset;
   1077 	} else if (sock->tokens.num_tokens == 0) {
   1078 		sock->read_len = 0;
   1079 		sock->read_cur = 0;
   1080 	}
   1081 	return retval;
   1082 }
   1083 
   1084 LTK_GEN_LOG_FUNCS(ltkd, ltkd_log_msg, ltkd_cleanup)