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