ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

util.c (9802B)


      1 /*
      2  * Copyright (c) 2021, 2023 lumidify <nobody@lumidify.org>
      3  *
      4  * Permission to use, copy, modify, and/or distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <pwd.h>
     18 #include <fcntl.h>
     19 #include <ctype.h>
     20 #include <errno.h>
     21 #include <stdio.h>
     22 #include <stdlib.h>
     23 #include <string.h>
     24 #include <stdarg.h>
     25 #include <unistd.h>
     26 #include <sys/stat.h>
     27 
     28 #include "util.h"
     29 #include "array.h"
     30 #include "memory.h"
     31 #include "txtbuf.h"
     32 
     33 /* FIXME: Should these functions really fail on memory error? */
     34 
     35 char *
     36 ltk_read_file(const char *filename, size_t *len_ret, char **errstr_ret) {
     37 	long len;
     38 	char *file_contents;
     39 	FILE *file;
     40 
     41 	/* FIXME: https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file */
     42 	file = fopen(filename, "r");
     43 	if (!file) goto error;
     44 	if (fseek(file, 0, SEEK_END)) goto errorclose;
     45 	len = ftell(file);
     46 	if (len < 0) goto errorclose;
     47 	if (fseek(file, 0, SEEK_SET)) goto errorclose;
     48 	file_contents = ltk_malloc((size_t)len + 1);
     49 	clearerr(file);
     50 	fread(file_contents, 1, (size_t)len, file);
     51 	if (ferror(file)) goto errorclose;
     52 	file_contents[len] = '\0';
     53 	if (fclose(file)) goto error;
     54 	*len_ret = (size_t)len;
     55 	return file_contents;
     56 error:
     57 	if (errstr_ret)
     58 		*errstr_ret = strerror(errno);
     59 	return NULL;
     60 errorclose:
     61 	if (errstr_ret)
     62 		*errstr_ret = strerror(errno);
     63 	fclose(file);
     64 	return NULL;
     65 }
     66 
     67 /* FIXME: not sure if errno actually is set usefully after all these functions */
     68 int
     69 ltk_write_file(const char *path, const char *data, size_t len, char **errstr_ret) {
     70 	FILE *file = fopen(path, "w");
     71 	if (!file) goto error;
     72 	clearerr(file);
     73 	if (fwrite(data, 1, len, file) < len) goto errorclose;
     74 	if (fclose(file)) goto error;
     75 	return 0;
     76 error:
     77 	if (errstr_ret)
     78 		*errstr_ret = strerror(errno);
     79 	return 1;
     80 errorclose:
     81 	if (errstr_ret)
     82 		*errstr_ret = strerror(errno);
     83 	fclose(file);
     84 	return 1;
     85 }
     86 
     87 /* FIXME: maybe have a few standard array types defined somewhere else */
     88 LTK_ARRAY_INIT_DECL_STATIC(cmd, char *)
     89 LTK_ARRAY_INIT_IMPL_STATIC(cmd, char *)
     90 
     91 static void
     92 free_helper(char *ptr) {
     93 	ltk_free(ptr);
     94 }
     95 
     96 /* FIXME: this is really ugly */
     97 /* FIXME: parse command only once in beginning instead of each time it is run? */
     98 /* FIXME: this handles double-quote, but the config parser already uses that, so
     99    it's kind of weird because it's parsed twice (also backslashes are parsed twice). */
    100 int
    101 ltk_parse_run_cmd(const char *cmdtext, size_t len, const char *filename) {
    102 	int bs = 0;
    103 	int in_sqstr = 0;
    104 	int in_dqstr = 0;
    105 	int in_ws = 1;
    106 	char c;
    107 	size_t cur_start = 0;
    108 	int offset = 0;
    109 	txtbuf *cur_arg = txtbuf_new();
    110 	ltk_array(cmd) *cmd = ltk_array_create(cmd, 4);
    111 	char *cmdcopy = ltk_strndup(cmdtext, len);
    112 	for (size_t i = 0; i < len; i++) {
    113 		c = cmdcopy[i];
    114 		if (c == '\\') {
    115 			if (bs) {
    116 				offset++;
    117 				bs = 0;
    118 			} else {
    119 				bs = 1;
    120 			}
    121 		} else if (isspace(c)) {
    122 			if (!in_sqstr && !in_dqstr) {
    123 				if (bs) {
    124 					if (in_ws) {
    125 						in_ws = 0;
    126 						cur_start = i;
    127 						offset = 0;
    128 					} else {
    129 						offset++;
    130 					}
    131 					bs = 0;
    132 				} else if (!in_ws) {
    133 					/* FIXME: shouldn't this be < instead of <=? */
    134 					if (cur_start <= i - offset)
    135 						txtbuf_appendn(cur_arg, cmdcopy + cur_start, i - cur_start - offset);
    136 					/* FIXME: cmd is named horribly */
    137 					ltk_array_append(cmd, cmd, txtbuf_get_textcopy(cur_arg));
    138 					txtbuf_clear(cur_arg);
    139 					in_ws = 1;
    140 					offset = 0;
    141 				}
    142 			/* FIXME: parsing weird here - bs just ignored */
    143 			} else if (bs) {
    144 				bs = 0;
    145 			}
    146 		} else if (c == '%') {
    147 			if (bs) {
    148 				if (in_ws) {
    149 					cur_start = i;
    150 					offset = 0;
    151 				} else {
    152 					offset++;
    153 				}
    154 				bs = 0;
    155 			} else if (!in_sqstr && filename && i < len - 1 && cmdcopy[i + 1] == 'f') {
    156 				if (!in_ws && cur_start < i - offset)
    157 					txtbuf_appendn(cur_arg, cmdcopy + cur_start, i - cur_start - offset);
    158 				txtbuf_append(cur_arg, filename);
    159 				i++;
    160 				cur_start = i + 1;
    161 				offset = 0;
    162 			} else if (in_ws) {
    163 				cur_start = i;
    164 				offset = 0;
    165 			}
    166 			in_ws = 0;
    167 		} else if (c == '"') {
    168 			if (in_sqstr) {
    169 				bs = 0;
    170 			} else if (bs) {
    171 				if (in_ws) {
    172 					cur_start = i;
    173 					offset = 0;
    174 				} else {
    175 					offset++;
    176 				}
    177 				bs = 0;
    178 			} else if (in_dqstr) {
    179 				offset++;
    180 				in_dqstr = 0;
    181 				continue;
    182 			} else {
    183 				in_dqstr = 1;
    184 				if (in_ws) {
    185 					cur_start = i + 1;
    186 					offset = 0;
    187 				} else {
    188 					offset++;
    189 					continue;
    190 				}
    191 			}
    192 			in_ws = 0;
    193 		} else if (c == '\'') {
    194 			if (in_dqstr) {
    195 				bs = 0;
    196 			} else if (bs) {
    197 				if (in_ws) {
    198 					cur_start = i;
    199 					offset = 0;
    200 				} else {
    201 					offset++;
    202 				}
    203 				bs = 0;
    204 			} else if (in_sqstr) {
    205 				offset++;
    206 				in_sqstr = 0;
    207 				continue;
    208 			} else {
    209 				in_sqstr = 1;
    210 				if (in_ws) {
    211 					cur_start = i + 1;
    212 					offset = 0;
    213 				} else {
    214 					offset++;
    215 					continue;
    216 				}
    217 			}
    218 			in_ws = 0;
    219 		} else if (bs) {
    220 			if (!in_sqstr && !in_dqstr) {
    221 				if (in_ws) {
    222 					cur_start = i;
    223 					offset = 0;
    224 				} else {
    225 					offset++;
    226 				}
    227 			}
    228 			bs = 0;
    229 			in_ws = 0;
    230 		} else {
    231 			if (in_ws) {
    232 				cur_start = i;
    233 				offset = 0;
    234 			}
    235 			in_ws = 0;
    236 		}
    237 		cmdcopy[i - offset] = cmdcopy[i];
    238 	}
    239 	if (in_sqstr || in_dqstr) {
    240 		ltk_warn("Unterminated string in command\n");
    241 		goto error;
    242 	}
    243 	if (!in_ws) {
    244 		if (cur_start <= len - offset)
    245 			txtbuf_appendn(cur_arg, cmdcopy + cur_start, len - cur_start - offset);
    246 		ltk_array_append(cmd, cmd, txtbuf_get_textcopy(cur_arg));
    247 	}
    248 	if (cmd->len == 0) {
    249 		ltk_warn("Empty command\n");
    250 		goto error;
    251 	}
    252 	ltk_array_append(cmd, cmd, NULL); /* necessary for execvp */
    253 	int fret = -1;
    254 	if ((fret = fork()) < 0) {
    255 		ltk_warn("Unable to fork\n");
    256 		goto error;
    257 	} else if (fret == 0) {
    258 		if (execvp(cmd->buf[0], cmd->buf) == -1) {
    259 			/* FIXME: what to do on error here? */
    260 			exit(1);
    261 		}
    262 	} else {
    263 		ltk_free(cmdcopy);
    264 		txtbuf_destroy(cur_arg);
    265 		ltk_array_destroy_deep(cmd, cmd, &free_helper);
    266 		return fret;
    267 	}
    268 error:
    269 	ltk_free(cmdcopy);
    270 	txtbuf_destroy(cur_arg);
    271 	ltk_array_destroy_deep(cmd, cmd, &free_helper);
    272 	return -1;
    273 }
    274 
    275 /* If `needed` is larger than `*alloc_size`, resize `*str` to
    276    `max(needed, *alloc_size * 2)`. Aborts program on error. */
    277 void
    278 ltk_grow_string(char **str, int *alloc_size, int needed) {
    279         if (needed <= *alloc_size) return;
    280         int new_size = needed > (*alloc_size * 2) ? needed : (*alloc_size * 2);
    281         char *new = ltk_realloc(*str, new_size);
    282         *str = new;
    283         *alloc_size = new_size;
    284 }
    285 
    286 /* Get the directory to store ltk files in and create it if it doesn't exist yet.
    287    This first checks the environment variable LTKDIR and, if that doesn't
    288    exist, the home directory with "/.ltk" appended.
    289    Returns NULL on error. */
    290 char *
    291 ltk_setup_directory(void) {
    292 	char *dir, *dir_orig;
    293 	struct passwd *pw;
    294 	uid_t uid;
    295 	int len;
    296 
    297 	dir_orig = getenv("LTKDIR");
    298 	if (dir_orig) {
    299 		dir = ltk_strdup(dir_orig);
    300 		/*
    301 		if (!dir)
    302 			return NULL;
    303 		*/
    304 	} else {
    305 		uid = getuid();
    306 		pw = getpwuid(uid);
    307 		if (!pw)
    308 			return NULL;
    309 		len = strlen(pw->pw_dir);
    310 		dir = ltk_malloc(len + 6);
    311 		/*
    312 		if (!dir)
    313 			return NULL;
    314 		*/
    315 		strcpy(dir, pw->pw_dir);
    316 		strcpy(dir + len, "/.ltk");
    317 	}
    318 
    319 	if (mkdir(dir, 0770) < 0) {
    320 		if (errno != EEXIST)
    321 			return NULL;
    322 	}
    323 
    324 	return dir;
    325 }
    326 
    327 /* Concatenate the two given strings and return the result.
    328    This allocates new memory for the result string, unlike
    329    the actual strcat. Aborts program on error */
    330 char *
    331 ltk_strcat_useful(const char *str1, const char *str2) {
    332 	int len1, len2;
    333 	char *ret;
    334 
    335 	len1 = strlen(str1);
    336 	len2 = strlen(str2);
    337 	ret = ltk_malloc(len1 + len2 + 1);
    338 	strcpy(ret, str1);
    339 	strcpy(ret + len1, str2);
    340 
    341 	return ret;
    342 }
    343 
    344 void
    345 ltk_warn(const char *format, ...) {
    346 	va_list args;
    347 	va_start(args, format);
    348 	ltk_log_msg("Warning", format, args);
    349 	va_end(args);
    350 }
    351 
    352 void
    353 ltk_fatal(const char *format, ...) {
    354 	va_list args;
    355 	va_start(args, format);
    356 	ltk_log_msg("Fatal", format, args);
    357 	va_end(args);
    358 	ltk_cleanup();
    359 
    360 	exit(1);
    361 }
    362 
    363 void
    364 ltk_warn_errno(const char *format, ...) {
    365 	va_list args;
    366 	char *errstr = strerror(errno);
    367 	va_start(args, format);
    368 	ltk_log_msg("Warning", format, args);
    369 	va_end(args);
    370 	ltk_warn("system error: %s\n", errstr);
    371 }
    372 
    373 void
    374 ltk_fatal_errno(const char *format, ...) {
    375 	va_list args;
    376 	char *errstr = strerror(errno);
    377 	va_start(args, format);
    378 	ltk_log_msg("Fatal", format, args);
    379 	va_end(args);
    380 	ltk_fatal("system error: %s\n", errstr);
    381 }
    382 
    383 int
    384 str_array_equal(const char *terminated, const char *array, size_t len) {
    385 	if (!strncmp(terminated, array, len)) {
    386 		/* this is kind of inefficient, but there's no way to know
    387 		   otherwise if strncmp just stopped comparing after a '\0' */
    388 		return strlen(terminated) == len;
    389 	}
    390 	return 0;
    391 }
    392 
    393 size_t
    394 prev_utf8(char *text, size_t index) {
    395 	if (index == 0)
    396 		return 0;
    397 	size_t i = index - 1;
    398 	/* find valid utf8 char - this probably needs to be improved */
    399 	while (i > 0 && ((text[i] & 0xC0) == 0x80))
    400 		i--;
    401 	return i;
    402 }
    403 
    404 size_t
    405 next_utf8(char *text, size_t len, size_t index) {
    406 	if (index >= len)
    407 		return len;
    408 	size_t i = index + 1;
    409 	while (i < len && ((text[i] & 0xC0) == 0x80))
    410 		i++;
    411 	return i;
    412 }
    413 
    414 int
    415 set_nonblock(int fd) {
    416 	int flags = fcntl(fd, F_GETFL, 0);
    417 	if (flags == -1)
    418 		return -1;
    419 	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK))
    420 		return -1;
    421 	return 0;
    422 }