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