1 #include <stdlib.h>
2 #include <string.h>
3 #include <stdarg.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <sys/wait.h>
7 #include <unistd.h>
8 #include <errno.h>
9 #include "mle.h"
10
11 // Run a shell command, optionally feeding stdin, collecting stdout
12 // Specify timeout_s=-1 for no timeout
util_shell_exec(editor_t * editor,char * cmd,long timeout_s,char * input,size_t input_len,int setsid,char * opt_shell,char ** optret_output,size_t * optret_output_len)13 int util_shell_exec(editor_t *editor, char *cmd, long timeout_s, char *input, size_t input_len, int setsid, char *opt_shell, char **optret_output, size_t *optret_output_len) {
14 // TODO clean this crap up
15 int rv;
16 int do_read;
17 int do_write;
18 int readfd;
19 int writefd;
20 ssize_t rc;
21 ssize_t nbytes;
22 fd_set readfds;
23 struct timeval timeout;
24 struct timeval *timeoutptr;
25 pid_t pid;
26 str_t readbuf = {0};
27
28 do_read = optret_output != NULL ? 1 : 0;
29 do_write = input && input_len > 0 ? 1 : 0;
30 readfd = -1;
31 writefd = -1;
32 pid = -1;
33 readbuf.inc = -2; // double capacity on each allocation
34 rv = MLE_OK;
35 nbytes = 0;
36 if (do_read) {
37 *optret_output = NULL;
38 *optret_output_len = 0;
39 }
40
41 // Open cmd
42 if (!util_popen2(
43 cmd,
44 setsid,
45 opt_shell,
46 do_read ? &readfd : NULL,
47 do_write ? &writefd : NULL,
48 &pid
49 )) {
50 MLE_RETURN_ERR(editor, "Failed to exec shell cmd: %s", cmd);
51 }
52
53 // Read-write loop
54 do {
55 // Write to shell cmd if input is remaining
56 if (do_write && writefd >= 0) {
57 rc = write(writefd, input, input_len);
58 if (rc > 0) {
59 input += rc;
60 input_len -= rc;
61 if (input_len < 1) {
62 close(writefd);
63 writefd = -1;
64 }
65 } else {
66 // write err
67 MLE_SET_ERR(editor, "write error: %s", strerror(errno));
68 rv = MLE_ERR;
69 break;
70 }
71 }
72
73 // Read shell cmd, timing out after timeout_sec
74 if (do_read) {
75 if (timeout_s >= 0) {
76 timeout.tv_sec = timeout_s;
77 timeout.tv_usec = 0;
78 timeoutptr = &timeout;
79 } else {
80 timeoutptr = NULL;
81 }
82 FD_ZERO(&readfds);
83 FD_SET(readfd, &readfds);
84 rc = select(readfd + 1, &readfds, NULL, NULL, timeoutptr);
85 if (rc < 0) {
86 // Err on select
87 MLE_SET_ERR(editor, "select error: %s", strerror(errno));
88 rv = MLE_ERR;
89 break;
90 } else if (rc == 0) {
91 // Timed out
92 rv = MLE_ERR;
93 break;
94 } else {
95 // Read a kilobyte of data
96 str_ensure_cap(&readbuf, readbuf.len + 1024);
97 nbytes = read(readfd, readbuf.data + readbuf.len, 1024);
98 if (nbytes < 0) {
99 // read err or EAGAIN/EWOULDBLOCK
100 MLE_SET_ERR(editor, "read error: %s", strerror(errno));
101 rv = MLE_ERR;
102 break;
103 } else if (nbytes > 0) {
104 // Got data
105 readbuf.len += nbytes;
106 }
107 }
108 }
109 } while(nbytes > 0); // until EOF
110
111 // Close pipes and reap child proc
112 if (readfd >= 0) close(readfd);
113 if (writefd >= 0) close(writefd);
114 waitpid(pid, NULL, do_read ? WNOHANG : 0);
115
116 if (do_read) {
117 *optret_output = readbuf.data;
118 *optret_output_len = readbuf.len;
119 }
120
121 return rv;
122 }
123
124 // Like popen, but more control over pipes. Returns 1 on success, 0 on failure.
util_popen2(char * cmd,int do_setsid,char * opt_shell,int * optret_fdread,int * optret_fdwrite,pid_t * optret_pid)125 int util_popen2(char *cmd, int do_setsid, char *opt_shell, int *optret_fdread, int *optret_fdwrite, pid_t *optret_pid) {
126 pid_t pid;
127 int do_read;
128 int do_write;
129 int pout[2];
130 int pin[2];
131
132 // Set r/w
133 do_read = optret_fdread != NULL ? 1 : 0;
134 do_write = optret_fdwrite != NULL ? 1 : 0;
135
136 // Set shell
137 opt_shell = opt_shell ? opt_shell : "sh";
138
139 // Make pipes
140 if (do_read) if (pipe(pout)) return 0;
141 if (do_write) if (pipe(pin)) return 0;
142
143 // Fork
144 pid = fork();
145 if (pid < 0) {
146 // Fork failed
147 return 0;
148 } else if (pid == 0) {
149 // Child
150 if (do_read) {
151 close(pout[0]);
152 dup2(pout[1], STDOUT_FILENO);
153 close(pout[1]);
154 }
155 if (do_write) {
156 close(pin[1]);
157 dup2(pin[0], STDIN_FILENO);
158 close(pin[0]);
159 }
160 if (do_setsid) setsid();
161 execlp(opt_shell, opt_shell, "-c", cmd, NULL);
162 exit(EXIT_FAILURE);
163 }
164 // Parent
165 if (do_read) {
166 close(pout[1]);
167 *optret_fdread = pout[0];
168 }
169 if (do_write) {
170 close(pin[0]);
171 *optret_fdwrite = pin[1];
172 }
173 if (optret_pid) *optret_pid = pid;
174 return 1;
175 }
176
177 // Return paired bracket if ch is a bracket, else return 0
util_get_bracket_pair(uint32_t ch,int * optret_is_closing)178 int util_get_bracket_pair(uint32_t ch, int *optret_is_closing) {
179 switch (ch) {
180 case '[': if (optret_is_closing) *optret_is_closing = 0; return ']';
181 case '(': if (optret_is_closing) *optret_is_closing = 0; return ')';
182 case '{': if (optret_is_closing) *optret_is_closing = 0; return '}';
183 case ']': if (optret_is_closing) *optret_is_closing = 1; return '[';
184 case ')': if (optret_is_closing) *optret_is_closing = 1; return '(';
185 case '}': if (optret_is_closing) *optret_is_closing = 1; return '{';
186 default: return 0;
187 }
188 return 0;
189 }
190
191 // Return 1 if path is file
util_is_file(char * path,char * opt_mode,FILE ** optret_file)192 int util_is_file(char *path, char *opt_mode, FILE **optret_file) {
193 struct stat sb;
194 if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) return 0;
195 if (opt_mode && optret_file) {
196 *optret_file = fopen(path, opt_mode);
197 if (!*optret_file) return 0;
198 }
199 return 1;
200 }
201
202 // Return 1 if path is dir
util_is_dir(char * path)203 int util_is_dir(char *path) {
204 struct stat sb;
205 if (stat(path, &sb) != 0 || !S_ISDIR(sb.st_mode)) return 0;
206 return 1;
207 }
208
209 // Return 1 if re matches subject
util_pcre_match(char * re,char * subject,int subject_len,char ** optret_capture,int * optret_capture_len)210 int util_pcre_match(char *re, char *subject, int subject_len, char **optret_capture, int *optret_capture_len) {
211 int rc;
212 pcre *cre;
213 const char *error;
214 int erroffset;
215 int ovector[3];
216 cre = pcre_compile((const char*)re, (optret_capture ? 0 : PCRE_NO_AUTO_CAPTURE) | PCRE_CASELESS, &error, &erroffset, NULL);
217 if (!cre) return 0;
218 rc = pcre_exec(cre, NULL, subject, subject_len, 0, 0, ovector, 3);
219 pcre_free(cre);
220 if (optret_capture) {
221 if (rc >= 0) {
222 *optret_capture = subject + ovector[0];
223 *optret_capture_len = ovector[1] - ovector[0];
224 } else {
225 *optret_capture = NULL;
226 *optret_capture_len = 0;
227 }
228 }
229 return rc >= 0 ? 1 : 0;
230 }
231
232 // Perform a regex replace with back-references. Return number of replacements
233 // made. If regex is invalid, `ret_result` is set to NULL, `ret_result_len` is
234 // set to 0 and 0 is returned.
util_pcre_replace(char * re,char * subj,char * repl,char ** ret_result,int * ret_result_len)235 int util_pcre_replace(char *re, char *subj, char *repl, char **ret_result, int *ret_result_len) {
236 int rc;
237 pcre *cre;
238 const char *error;
239 int erroffset;
240 int subj_offset;
241 int subj_offset_z;
242 int subj_len;
243 int subj_look_offset;
244 int last_look_offset;
245 int ovector[30];
246 int num_repls;
247 int got_match = 0;
248 str_t result = {0};
249
250 *ret_result = NULL;
251 *ret_result_len = 0;
252
253 // Compile regex
254 cre = pcre_compile((const char*)re, PCRE_CASELESS, &error, &erroffset, NULL);
255 if (!cre) return 0;
256
257 // Start match-replace loop
258 num_repls = 0;
259 subj_len = strlen(subj);
260 subj_offset = 0;
261 subj_offset_z = 0;
262 subj_look_offset = 0;
263 last_look_offset = 0;
264 while (subj_offset < subj_len) {
265 // Find match
266 rc = pcre_exec(cre, NULL, subj, subj_len, subj_look_offset, 0, ovector, 30);
267 if (rc < 0 || ovector[0] < 0) {
268 got_match = 0;
269 subj_offset_z = subj_len;
270 } else {
271 got_match = 1;
272 subj_offset_z = ovector[0];
273 }
274
275 // Append part before match
276 str_append_stop(&result, subj + subj_offset, subj + subj_offset_z);
277 subj_offset = ovector[1];
278 subj_look_offset = subj_offset + (subj_offset > last_look_offset ? 0 : 1); // Prevent infinite loop
279 last_look_offset = subj_look_offset;
280
281 // Break if no match
282 if (!got_match) break;
283
284 // Append replacements with backrefs
285 str_append_replace_with_backrefs(&result, subj, repl, rc, ovector, 30);
286
287 // Increment num_repls
288 num_repls += 1;
289 }
290
291 // Free regex
292 pcre_free(cre);
293
294 // Return result
295 *ret_result = result.data ? result.data : strdup("");
296 *ret_result_len = result.len;
297
298 // Return number of replacements
299 return num_repls;
300 }
301
302 // Return 1 if a > b, else return 0.
util_timeval_is_gt(struct timeval * a,struct timeval * b)303 int util_timeval_is_gt(struct timeval *a, struct timeval *b) {
304 if (a->tv_sec > b->tv_sec) {
305 return 1;
306 } else if (a->tv_sec == b->tv_sec) {
307 return a->tv_usec > b->tv_usec ? 1 : 0;
308 }
309 return 0;
310 }
311
312 // Ported from php_escape_shell_arg
313 // https://github.com/php/php-src/blob/master/ext/standard/exec.c
util_escape_shell_arg(char * str,int l)314 char *util_escape_shell_arg(char *str, int l) {
315 int x, y = 0;
316 char *cmd;
317
318 cmd = malloc(4 * l + 3); // worst case
319
320 cmd[y++] = '\'';
321
322 for (x = 0; x < l; x++) {
323 int mb_len = tb_utf8_char_length(*(str + x));
324
325 // skip non-valid multibyte characters
326 if (mb_len < 0) {
327 continue;
328 } else if (mb_len > 1) {
329 memcpy(cmd + y, str + x, mb_len);
330 y += mb_len;
331 x += mb_len - 1;
332 continue;
333 }
334
335 switch (str[x]) {
336 case '\'':
337 cmd[y++] = '\'';
338 cmd[y++] = '\\';
339 cmd[y++] = '\'';
340 // fall-through
341 default:
342 cmd[y++] = str[x];
343 }
344 }
345 cmd[y++] = '\'';
346 cmd[y] = '\0';
347
348 return cmd;
349 }
350
351 // Adapted from termbox src/demo/keyboard.c
tb_print(int x,int y,uint16_t fg,uint16_t bg,char * str)352 int tb_print(int x, int y, uint16_t fg, uint16_t bg, char *str) {
353 uint32_t uni;
354 int c;
355 c = 0;
356 while (*str) {
357 str += utf8_char_to_unicode(&uni, str, NULL);
358 tb_change_cell(x, y, uni, fg, bg);
359 x++;
360 c++;
361 }
362 return c;
363 }
364
365 // Adapted from termbox src/demo/keyboard.c
tb_printf(bview_rect_t rect,int x,int y,uint16_t fg,uint16_t bg,const char * fmt,...)366 int tb_printf(bview_rect_t rect, int x, int y, uint16_t fg, uint16_t bg, const char *fmt, ...) {
367 char buf[4096];
368 va_list vl;
369 va_start(vl, fmt);
370 vsnprintf(buf, sizeof(buf), fmt, vl);
371 va_end(vl);
372 return tb_print(rect.x + x, rect.y + y, fg ? fg : rect.fg, bg ? bg : rect.bg, buf);
373 }
374
375 // Like tb_printf, but accepts @fg,bg; attributes inside the string. To print
376 // a literal '@', use '@@' in the format string. Specify fg or bg of 0 to
377 // reset that attribute.
tb_printf_attr(bview_rect_t rect,int x,int y,const char * fmt,...)378 int tb_printf_attr(bview_rect_t rect, int x, int y, const char *fmt, ...) {
379 char bufo[4096];
380 char *buf;
381 int fg;
382 int bg;
383 int tfg;
384 int tbg;
385 int c;
386 uint32_t uni;
387
388 va_list vl;
389 va_start(vl, fmt);
390 vsnprintf(bufo, sizeof(bufo), fmt, vl);
391 va_end(vl);
392
393 fg = rect.fg;
394 bg = rect.bg;
395 x = rect.x + x;
396 y = rect.y + y;
397
398 c = 0;
399 buf = bufo;
400 while (*buf) {
401 buf += utf8_char_to_unicode(&uni, buf, NULL);
402 if (uni == '@') {
403 if (!*buf) break;
404 utf8_char_to_unicode(&uni, buf, NULL);
405 if (uni != '@') {
406 tfg = strtol(buf, &buf, 10);
407 if (!*buf) break;
408 utf8_char_to_unicode(&uni, buf, NULL);
409 if (uni == ',') {
410 buf++;
411 if (!*buf) break;
412 tbg = strtol(buf, &buf, 10);
413 fg = tfg <= 0 ? rect.fg : tfg;
414 bg = tbg <= 0 ? rect.bg : tbg;
415 if (!*buf) break;
416 utf8_char_to_unicode(&uni, buf, NULL);
417 if (uni == ';') buf++;
418 continue;
419 }
420 }
421 }
422 tb_change_cell(x, y, uni, fg, bg);
423 x++;
424 c++;
425 }
426 return c;
427 }
428
429
430 // Zero-fill realloc
recalloc(void * ptr,size_t orig_num,size_t new_num,size_t el_size)431 void *recalloc(void *ptr, size_t orig_num, size_t new_num, size_t el_size) {
432 void *newptr;
433 newptr = realloc(ptr, new_num * el_size);
434 if (!newptr) return NULL;
435 if (new_num > orig_num) {
436 memset(newptr + (orig_num * el_size), 0, (new_num - orig_num) * el_size);
437 }
438 return newptr;
439 }
440
441 // Append from data up until data_stop to str
str_append_stop(str_t * str,char * data,char * data_stop)442 void str_append_stop(str_t *str, char *data, char *data_stop) {
443 size_t data_len;
444 data_len = data_stop >= data ? data_stop - data : 0;
445 str_append_len(str, data, data_len);
446 }
447
448 // Append data to str
str_append(str_t * str,char * data)449 void str_append(str_t *str, char *data) {
450 str_append_len(str, data, strlen(data));
451 }
452
453 // Append data_len bytes of data to str
str_append_len(str_t * str,char * data,size_t data_len)454 void str_append_len(str_t *str, char *data, size_t data_len) {
455 str_put_len(str, data, data_len, 0);
456 }
457
458 // Append char to str
str_append_char(str_t * str,char c)459 void str_append_char(str_t *str, char c) {
460 str_put_len(str, &c, 1, 0);
461 }
462
463 // Prepend from data up until data_stop to str
str_prepend_stop(str_t * str,char * data,char * data_stop)464 void str_prepend_stop(str_t *str, char *data, char *data_stop) {
465 size_t data_len;
466 data_len = data_stop >= data ? data_stop - data : 0;
467 str_prepend_len(str, data, data_len);
468 }
469
470 // Prepend data to str
str_prepend(str_t * str,char * data)471 void str_prepend(str_t *str, char *data) {
472 str_prepend_len(str, data, strlen(data));
473 }
474
475 // Prepend data_len bytes of data to str
str_prepend_len(str_t * str,char * data,size_t data_len)476 void str_prepend_len(str_t *str, char *data, size_t data_len) {
477 str_put_len(str, data, data_len, 1);
478 }
479
480 // Set str to data
str_set(str_t * str,char * data)481 void str_set(str_t *str, char *data) {
482 str_set_len(str, data, strlen(data));
483 }
484
485 // Set str to data for data_len bytes
str_set_len(str_t * str,char * data,size_t data_len)486 void str_set_len(str_t *str, char *data, size_t data_len) {
487 str_ensure_cap(str, data_len+1);
488 memcpy(str->data, data, data_len);
489 str->len = data_len;
490 *(str->data + str->len) = '\0';
491 }
492
493 // Append/prepend data_len bytes of data to str
str_put_len(str_t * str,char * data,size_t data_len,int is_prepend)494 void str_put_len(str_t *str, char *data, size_t data_len, int is_prepend) {
495 size_t req_cap;
496 req_cap = str->len + data_len + 1;
497 if (req_cap > str->cap) {
498 str_ensure_cap(str, req_cap);
499 }
500 if (is_prepend) {
501 memmove(str->data + data_len, str->data, str->len);
502 memcpy(str->data, data, data_len);
503 } else {
504 memcpy(str->data + str->len, data, data_len);
505 }
506 str->len += data_len;
507 *(str->data + str->len) = '\0';
508 }
509
510 // Ensure space in str
str_ensure_cap(str_t * str,size_t cap)511 void str_ensure_cap(str_t *str, size_t cap) {
512 if (cap > str->cap) {
513 if (str->inc >= 0) {
514 // If inc is positive, grow linearly
515 cap = MLBUF_MAX(cap, str->cap + (str->inc > 0 ? str->inc : 128));
516 } else {
517 // If inc is negative, grow geometrically
518 cap = MLBUF_MAX(cap, str->cap * (str->inc <= -2 ? str->inc * -1 : 2));
519 }
520 str->data = realloc(str->data, cap);
521 str->cap = cap;
522 }
523 }
524
525 // Clear str
str_clear(str_t * str)526 void str_clear(str_t *str) {
527 str->len = 0;
528 }
529
530 // Free str
str_free(str_t * str)531 void str_free(str_t *str) {
532 if (str->data) free(str->data);
533 memset(str, 0, sizeof(str_t));
534 }
535
536 // Replace `repl` in `subj` and append result to `str`. PCRE style backrefs are
537 // supported.
538 //
539 // str where to append data
540 // subj subject string
541 // repl replacement string with $1 or \1 style backrefs
542 // pcre_rc return code from pcre_exec
543 // pcre_ovector ovector used with pcre_exec
544 // pcre_ovecsize size of pcre_ovector
545 //
str_append_replace_with_backrefs(str_t * str,char * subj,char * repl,int pcre_rc,int * pcre_ovector,int pcre_ovecsize)546 void str_append_replace_with_backrefs(str_t *str, char *subj, char *repl, int pcre_rc, int *pcre_ovector, int pcre_ovecsize) {
547 char *repl_stop;
548 char *repl_cur;
549 char *repl_z;
550 char *repl_backref;
551 int repl_delta;
552 int ibackref;
553 char *term;
554 char *term_stop;
555 char hex[3];
556 char byte;
557
558 repl_stop = repl + strlen(repl);
559
560 // Start replace loop
561 repl_cur = repl;
562 while (repl_cur < repl_stop) {
563 // Find backref marker (dollar sign or backslash) in replacement str
564 repl_backref = strpbrk(repl_cur, "$\\");
565 repl_z = repl_backref ? repl_backref : repl_stop;
566
567 // Append part before backref
568 str_append_stop(str, repl_cur, repl_z);
569
570 // Break if no backref
571 if (!repl_backref) break;
572
573 // Append backref
574 term = NULL;
575 repl_delta = 2; // marker + backref symbol
576 if (repl_backref+1 >= repl_stop) {
577 // No data after backref marker; append the marker itself
578 term = repl_backref;
579 term_stop = repl_stop;
580 } else if (*(repl_backref+1) >= '0' && *(repl_backref+1) <= '9') {
581 // $N; append Nth captured substring from match
582 ibackref = *(repl_backref+1) - '0';
583 if (ibackref < pcre_rc && ibackref < pcre_ovecsize/3) {
584 // Backref exists
585 term = subj + pcre_ovector[ibackref*2];
586 term_stop = subj + pcre_ovector[ibackref*2 + 1];
587 } else {
588 // Backref does not exist; append marker + whatever character it was
589 term = repl_backref;
590 term_stop = term + utf8_char_length(*(term+1));
591 }
592 } else if (*(repl_backref+1) == 'n') {
593 // $n; append newline
594 term = "\n";
595 term_stop = term + 1;
596 } else if (*(repl_backref+1) == 't') {
597 // $t; append tab
598 term = "\t";
599 term_stop = term + 1;
600 } else if (*(repl_backref+1) == 'x' && repl_backref+3 < repl_stop) {
601 // $xNN; append byte
602 strncpy(hex, repl_backref+2, 2);
603 byte = strtoul(hex, NULL, 16);
604 term = &byte;
605 term_stop = term + 1;
606 repl_delta = 4; // marker + 'x' + d1 + d2
607 } else {
608 // $* (not number or 'n'); append marker + whatever character it was
609 term = repl_backref;
610 term_stop = term + utf8_char_length(*(term+1));
611 }
612 str_append_stop(str, term, term_stop);
613
614 // Advance repl_cur by repl_delta bytes
615 repl_cur = repl_backref + repl_delta;
616 }
617 }
618