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 }