1 /* radare - LGPL - Copyright 2020 - thestr4ng3r */
2
3 #include "r2r.h"
4
5 #if __WINDOWS__
6 struct r2r_subprocess_t {
7 HANDLE stdin_write;
8 HANDLE stdout_read;
9 HANDLE stderr_read;
10 HANDLE proc;
11 int ret;
12 RStrBuf out;
13 RStrBuf err;
14 };
15
16 static volatile long pipe_id = 0;
17
create_pipe_overlap(HANDLE * pipe_read,HANDLE * pipe_write,LPSECURITY_ATTRIBUTES attrs,DWORD sz,DWORD read_mode,DWORD write_mode)18 static bool create_pipe_overlap(HANDLE *pipe_read, HANDLE *pipe_write, LPSECURITY_ATTRIBUTES attrs, DWORD sz, DWORD read_mode, DWORD write_mode) {
19 // see https://stackoverflow.com/a/419736
20 if (!sz) {
21 sz = 4096;
22 }
23 char name[MAX_PATH];
24 snprintf (name, sizeof (name), "\\\\.\\pipe\\r2r-subproc.%d.%ld", (int)GetCurrentProcessId (), (long)InterlockedIncrement (&pipe_id));
25 *pipe_read = CreateNamedPipeA (name, PIPE_ACCESS_INBOUND | read_mode, PIPE_TYPE_BYTE | PIPE_WAIT, 1, sz, sz, 120 * 1000, attrs);
26 if (!*pipe_read) {
27 return FALSE;
28 }
29 *pipe_write = CreateFileA (name, GENERIC_WRITE, 0, attrs, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | write_mode, NULL);
30 if (*pipe_write == INVALID_HANDLE_VALUE) {
31 CloseHandle (*pipe_read);
32 return FALSE;
33 }
34 return true;
35 }
36
r2r_subprocess_init(void)37 R_API bool r2r_subprocess_init(void) { return true; }
r2r_subprocess_fini(void)38 R_API void r2r_subprocess_fini(void) {}
39
40 // Create an env block that inherits the current vars but overrides the given ones
override_env(const char * envvars[],const char * envvals[],size_t env_size)41 static LPWCH override_env(const char *envvars[], const char *envvals[], size_t env_size) {
42 LPWCH ret = NULL;
43 LPWCH parent_env = NULL;
44 size_t i;
45 LPWSTR *wenvvars = calloc (env_size, sizeof (LPWSTR));
46 LPWSTR *wenvvals = calloc (env_size, sizeof (LPWSTR));
47 parent_env = GetEnvironmentStringsW ();
48 if (!wenvvars || !wenvvals || !parent_env) {
49 goto error;
50 }
51
52 for (i = 0; i < env_size; i++) {
53 wenvvars[i] = r_utf8_to_utf16 (envvars[i]);
54 wenvvals[i] = r_utf8_to_utf16 (envvals[i]);
55 if (!wenvvars[i] || !wenvvals[i]) {
56 goto error;
57 }
58 }
59
60 RVector buf;
61 r_vector_init (&buf, sizeof (wchar_t), NULL, NULL);
62 LPWCH cur = parent_env;
63 while (true) {
64 LPWCH var_begin = cur;
65 //wprintf (L"ENV: %s\n", cur);
66 while (*cur && *cur != L'=') {
67 cur++;
68 }
69 if (!*cur) {
70 cur++;
71 if (!*cur) {
72 break;
73 }
74 continue;
75 }
76 bool overridden = false;
77 for (i = 0; i < env_size; i++) {
78 size_t overlen = lstrlenW (wenvvars[i]);
79 size_t curlen = cur - var_begin;
80 if (overlen == curlen && !memcmp (var_begin, wenvvars[i], overlen)) {
81 overridden = true;
82 break;
83 }
84 }
85 while (*cur) {
86 cur++;
87 }
88 if (!overridden) {
89 r_vector_insert_range (&buf, buf.len, var_begin, cur - var_begin + 1);
90 }
91 cur++;
92 if (!*cur) {
93 // \0\0 marks the end
94 break;
95 }
96 }
97
98 wchar_t c;
99 for (i = 0; i < env_size; i++) {
100 r_vector_insert_range (&buf, buf.len, wenvvars[i], lstrlenW (wenvvars[i]));
101 c = L'=';
102 r_vector_push (&buf, &c);
103 r_vector_insert_range (&buf, buf.len, wenvvals[i], lstrlenW (wenvvals[i]));
104 c = L'\0';
105 r_vector_push (&buf, &c);
106 }
107 c = '\0';
108 r_vector_push (&buf, &c);
109 ret = buf.a;
110
111 error:
112 if (parent_env) {
113 FreeEnvironmentStringsW (parent_env);
114 }
115 for (i = 0; i < env_size; i++) {
116 if (wenvvars) {
117 free (wenvvars[i]);
118 }
119 if (wenvvals) {
120 free (wenvvals[i]);
121 }
122 }
123 free (wenvvars);
124 free (wenvvals);
125 return ret;
126 }
127
r2r_subprocess_start(const char * file,const char * args[],size_t args_size,const char * envvars[],const char * envvals[],size_t env_size)128 R_API R2RSubprocess *r2r_subprocess_start(
129 const char *file, const char *args[], size_t args_size,
130 const char *envvars[], const char *envvals[], size_t env_size) {
131 R2RSubprocess *proc = NULL;
132 HANDLE stdin_read = NULL;
133 HANDLE stdout_write = NULL;
134 HANDLE stderr_write = NULL;
135
136 char **argv = calloc (args_size + 1, sizeof (char *));
137 if (!argv) {
138 return NULL;
139 }
140 argv[0] = (char *)file;
141 if (args_size) {
142 memcpy (argv + 1, args, sizeof (char *) * args_size);
143 }
144 char *cmdline = r_str_format_msvc_argv (args_size + 1, argv);
145 free (argv);
146 if (!cmdline) {
147 return NULL;
148 }
149
150 proc = R_NEW0 (R2RSubprocess);
151 if (!proc) {
152 goto error;
153 }
154 proc->ret = -1;
155
156 SECURITY_ATTRIBUTES sattrs;
157 sattrs.nLength = sizeof (sattrs);
158 sattrs.bInheritHandle = TRUE;
159 sattrs.lpSecurityDescriptor = NULL;
160
161 if (!create_pipe_overlap (&proc->stdout_read, &stdout_write, &sattrs, 0, FILE_FLAG_OVERLAPPED, 0)) {
162 proc->stdout_read = stdout_write = NULL;
163 goto error;
164 }
165 if (!SetHandleInformation (proc->stdout_read, HANDLE_FLAG_INHERIT, 0)) {
166 goto error;
167 }
168 if (!create_pipe_overlap (&proc->stderr_read, &stderr_write, &sattrs, 0, FILE_FLAG_OVERLAPPED, 0)) {
169 proc->stdout_read = stderr_write = NULL;
170 goto error;
171 }
172 if (!SetHandleInformation (proc->stderr_read, HANDLE_FLAG_INHERIT, 0)) {
173 goto error;
174 }
175 if (!CreatePipe (&stdin_read, &proc->stdin_write, &sattrs, 0)) {
176 stdin_read = proc->stdin_write = NULL;
177 goto error;
178 }
179 if (!SetHandleInformation (proc->stdin_write, HANDLE_FLAG_INHERIT, 0)) {
180 goto error;
181 }
182
183 PROCESS_INFORMATION proc_info = { 0 };
184 STARTUPINFOA start_info = { 0 };
185 start_info.cb = sizeof (start_info);
186 start_info.hStdError = stderr_write;
187 start_info.hStdOutput = stdout_write;
188 start_info.hStdInput = stdin_read;
189 start_info.dwFlags |= STARTF_USESTDHANDLES;
190
191 LPWSTR env = override_env (envvars, envvals, env_size);
192 if (!CreateProcessA (NULL, cmdline,
193 NULL, NULL, TRUE, CREATE_UNICODE_ENVIRONMENT, env,
194 NULL, &start_info, &proc_info)) {
195 free (env);
196 eprintf ("CreateProcess failed: %#x\n", (int)GetLastError ());
197 goto error;
198 }
199 free (env);
200
201 CloseHandle (proc_info.hThread);
202 proc->proc = proc_info.hProcess;
203
204 beach:
205 if (stdin_read) {
206 CloseHandle (stdin_read);
207 }
208 if (stdout_write) {
209 CloseHandle (stdout_write);
210 }
211 if (stderr_write) {
212 CloseHandle (stderr_write);
213 }
214 free (cmdline);
215 return proc;
216 error:
217 if (proc) {
218 if (proc->stdin_write) {
219 CloseHandle (proc->stdin_write);
220 }
221 if (proc->stdout_read) {
222 CloseHandle (proc->stdout_read);
223 }
224 if (proc->stderr_read) {
225 CloseHandle (proc->stderr_read);
226 }
227 free (proc);
228 proc = NULL;
229 }
230 goto beach;
231 }
232
r2r_subprocess_wait(R2RSubprocess * proc,ut64 timeout_ms)233 R_API bool r2r_subprocess_wait(R2RSubprocess *proc, ut64 timeout_ms) {
234 OVERLAPPED stdout_overlapped = { 0 };
235 stdout_overlapped.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
236 if (!stdout_overlapped.hEvent) {
237 return false;
238 }
239 OVERLAPPED stderr_overlapped = { 0 };
240 stderr_overlapped.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
241 if (!stderr_overlapped.hEvent) {
242 CloseHandle (stdout_overlapped.hEvent);
243 return false;
244 }
245
246 ut64 timeout_us_abs = UT64_MAX;
247 if (timeout_ms != UT64_MAX) {
248 timeout_us_abs = r_time_now_mono () + timeout_ms * R_USEC_PER_MSEC;
249 }
250
251 ut8 stdout_buf[0x500];
252 ut8 stderr_buf[0x500];
253 bool stdout_eof = false;
254 bool stderr_eof = false;
255 bool child_dead = false;
256
257 #define DO_READ(which) \
258 if (!ReadFile (proc->which##_read, which##_buf, sizeof (which##_buf) - 1, NULL, &(which##_overlapped))) { \
259 if (GetLastError () != ERROR_IO_PENDING) { \
260 /* EOF or some other error */ \
261 which##_eof = true; \
262 } \
263 }
264
265 DO_READ (stdout)
266 DO_READ (stderr)
267
268 RVector handles;
269 r_vector_init (&handles, sizeof (HANDLE), NULL, NULL);
270 while (true) {
271 r_vector_clear (&handles);
272 size_t stdout_index = 0;
273 size_t stderr_index = 0;
274 size_t proc_index = 0;
275 if (!stdout_eof) {
276 stdout_index = handles.len;
277 r_vector_push (&handles, &stdout_overlapped.hEvent);
278 }
279 if (!stderr_eof) {
280 stderr_index = handles.len;
281 r_vector_push (&handles, &stderr_overlapped.hEvent);
282 }
283 if (!child_dead) {
284 proc_index = handles.len;
285 r_vector_push (&handles, &proc->proc);
286 }
287
288 DWORD timeout = INFINITE;
289 if (timeout_us_abs != UT64_MAX) {
290 ut64 now = r_time_now_mono ();
291 if (now >= timeout_us_abs) {
292 return false;
293 }
294 timeout = (DWORD)((timeout_us_abs - now) / R_USEC_PER_MSEC);
295 }
296 DWORD signaled = WaitForMultipleObjects (handles.len, handles.a, FALSE, timeout);
297 if (!stdout_eof && signaled == stdout_index) {
298 DWORD r;
299 BOOL res = GetOverlappedResult (proc->stdout_read, &stdout_overlapped, &r, TRUE);
300 if (!res) {
301 stdout_eof = true;
302 continue;
303 }
304 stdout_buf[r] = '\0';
305 r_str_remove_char (stdout_buf, '\r');
306 r_strbuf_append (&proc->out, (const char *)stdout_buf);
307 ResetEvent (stdout_overlapped.hEvent);
308 DO_READ (stdout)
309 continue;
310 }
311 if (!stderr_eof && signaled == stderr_index) {
312 DWORD read;
313 BOOL res = GetOverlappedResult (proc->stderr_read, &stderr_overlapped, &read, TRUE);
314 if (!res) {
315 stderr_eof = true;
316 continue;
317 }
318 stderr_buf[read] = '\0';
319 r_str_remove_char (stderr_buf, '\r');
320 r_strbuf_append (&proc->err, (const char *)stderr_buf);
321 ResetEvent (stderr_overlapped.hEvent);
322 DO_READ (stderr);
323 continue;
324 }
325 if (!child_dead && signaled == proc_index) {
326 child_dead = true;
327 DWORD exit_code;
328 if (GetExitCodeProcess (proc->proc, &exit_code)) {
329 proc->ret = exit_code;
330 }
331 continue;
332 }
333 break;
334 }
335 r_vector_clear (&handles);
336 CloseHandle (stdout_overlapped.hEvent);
337 CloseHandle (stderr_overlapped.hEvent);
338 return stdout_eof && stderr_eof && child_dead;
339 }
340
r2r_subprocess_kill(R2RSubprocess * proc)341 R_API void r2r_subprocess_kill(R2RSubprocess *proc) {
342 TerminateProcess (proc->proc, 255);
343 }
344
r2r_subprocess_stdin_write(R2RSubprocess * proc,const ut8 * buf,size_t buf_size)345 R_API void r2r_subprocess_stdin_write(R2RSubprocess *proc, const ut8 *buf, size_t buf_size) {
346 DWORD read;
347 WriteFile (proc->stdin_write, buf, buf_size, &read, NULL);
348 }
349
350
r2r_subprocess_drain(R2RSubprocess * proc)351 R_API R2RProcessOutput *r2r_subprocess_drain(R2RSubprocess *proc) {
352 R2RProcessOutput *out = R_NEW (R2RProcessOutput);
353 if (!out) {
354 return NULL;
355 }
356 out->out = r_strbuf_drain_nofree (&proc->out);
357 out->err = r_strbuf_drain_nofree (&proc->err);
358 out->ret = proc->ret;
359 return out;
360 }
361
r2r_subprocess_free(R2RSubprocess * proc)362 R_API void r2r_subprocess_free(R2RSubprocess *proc) {
363 if (!proc) {
364 return;
365 }
366 CloseHandle (proc->stdin_write);
367 CloseHandle (proc->stdout_read);
368 CloseHandle (proc->stderr_read);
369 CloseHandle (proc->proc);
370 free (proc);
371 }
372 #else
373
374 #include <errno.h>
375 #include <sys/wait.h>
376
377 struct r2r_subprocess_t {
378 pid_t pid;
379 int stdin_fd;
380 int stdout_fd;
381 int stderr_fd;
382 int killpipe[2];
383 int ret;
384 RStrBuf out;
385 RStrBuf err;
386 };
387
388 static RPVector subprocs;
389 static RThreadLock *subprocs_mutex;
390 static int sigchld_pipe[2];
391 static RThread *sigchld_thread;
392
handle_sigchld(int sig)393 static void handle_sigchld(int sig) {
394 ut8 b = 1;
395 write (sigchld_pipe[1], &b, 1);
396 }
397
sigchld_th(RThread * th)398 static RThreadFunctionRet sigchld_th(RThread *th) {
399 while (true) {
400 ut8 b;
401 ssize_t rd = read (sigchld_pipe[0], &b, 1);
402 if (rd <= 0) {
403 if (rd < 0) {
404 if (errno == EINTR) {
405 continue;
406 }
407 perror ("read");
408 }
409 break;
410 }
411 if (!b) {
412 break;
413 }
414 while (true) {
415 int wstat;
416 pid_t pid = waitpid (-1, &wstat, WNOHANG);
417 if (pid <= 0)
418 break;
419
420 r_th_lock_enter (subprocs_mutex);
421 void **it;
422 R2RSubprocess *proc = NULL;
423 r_pvector_foreach (&subprocs, it) {
424 R2RSubprocess *p = *it;
425 if (p->pid == pid) {
426 proc = p;
427 break;
428 }
429 }
430 if (!proc) {
431 r_th_lock_leave (subprocs_mutex);
432 continue;
433 }
434
435 if (WIFEXITED (wstat)) {
436 proc->ret = WEXITSTATUS (wstat);
437 } else {
438 proc->ret = -1;
439 }
440 ut8 r = 0;
441 write (proc->killpipe[1], &r, 1);
442 r_th_lock_leave (subprocs_mutex);
443 }
444 }
445 return R_TH_STOP;
446 }
447
r2r_subprocess_init(void)448 R_API bool r2r_subprocess_init(void) {
449 r_pvector_init(&subprocs, NULL);
450 subprocs_mutex = r_th_lock_new (false);
451 if (!subprocs_mutex) {
452 return false;
453 }
454 if (pipe (sigchld_pipe) == -1) {
455 perror ("pipe");
456 r_th_lock_free (subprocs_mutex);
457 return false;
458 }
459 sigchld_thread = r_th_new (sigchld_th, NULL, 0);
460 if (!sigchld_thread) {
461 close (sigchld_pipe [0]);
462 close (sigchld_pipe [1]);
463 r_th_lock_free (subprocs_mutex);
464 return false;
465 }
466 if (r_sys_signal (SIGCHLD, handle_sigchld) < 0) {
467 close (sigchld_pipe [0]);
468 close (sigchld_pipe [1]);
469 r_th_lock_free (subprocs_mutex);
470 return false;
471 }
472 return true;
473 }
474
r2r_subprocess_fini(void)475 R_API void r2r_subprocess_fini(void) {
476 r_sys_signal (SIGCHLD, SIG_IGN);
477 ut8 b = 0;
478 write (sigchld_pipe[1], &b, 1);
479 close (sigchld_pipe [1]);
480 r_th_wait (sigchld_thread);
481 close (sigchld_pipe [0]);
482 r_th_free (sigchld_thread);
483 r_pvector_clear (&subprocs);
484 r_th_lock_free (subprocs_mutex);
485 }
486
r2r_subprocess_start(const char * file,const char * args[],size_t args_size,const char * envvars[],const char * envvals[],size_t env_size)487 R_API R2RSubprocess *r2r_subprocess_start(
488 const char *file, const char *args[], size_t args_size,
489 const char *envvars[], const char *envvals[], size_t env_size) {
490 char **argv = calloc (args_size + 2, sizeof (char *));
491 if (!argv) {
492 return NULL;
493 }
494 argv[0] = (char *)file;
495 if (args_size) {
496 memcpy (argv + 1, args, sizeof (char *) * args_size);
497 }
498 // done by calloc: argv[args_size + 1] = NULL;
499 r_th_lock_enter (subprocs_mutex);
500 R2RSubprocess *proc = R_NEW0 (R2RSubprocess);
501 if (!proc) {
502 goto error;
503 }
504 proc->killpipe[0] = proc->killpipe[1] = -1;
505 proc->ret = -1;
506 r_strbuf_init (&proc->out);
507 r_strbuf_init (&proc->err);
508
509 if (pipe (proc->killpipe) == -1) {
510 perror ("pipe");
511 goto error;
512 }
513 if (fcntl (proc->killpipe[1], F_SETFL, O_NONBLOCK) < 0) {
514 perror ("fcntl");
515 goto error;
516 }
517
518 int stdin_pipe[2] = { -1, -1 };
519 if (pipe (stdin_pipe) == -1) {
520 perror ("pipe");
521 goto error;
522 }
523 proc->stdin_fd = stdin_pipe[1];
524
525 int stdout_pipe[2] = { -1, -1 };
526 if (pipe (stdout_pipe) == -1) {
527 perror ("pipe");
528 goto error;
529 }
530 if (fcntl(stdout_pipe[0], F_SETFL, O_NONBLOCK) < 0) {
531 perror ("fcntl");
532 goto error;
533 }
534 proc->stdout_fd = stdout_pipe[0];
535
536 int stderr_pipe[2] = { -1, -1 };
537 if (pipe (stderr_pipe) == -1) {
538 perror ("pipe");
539 goto error;
540 }
541 if (fcntl(stderr_pipe[0], F_SETFL, O_NONBLOCK) < 0) {
542 perror ("fcntl");
543 goto error;
544 }
545 proc->stderr_fd = stderr_pipe[0];
546
547 proc->pid = r_sys_fork ();
548 if (proc->pid == -1) {
549 // fail
550 r_th_lock_leave (subprocs_mutex);
551 perror ("fork");
552 free (proc);
553 free (argv);
554 return NULL;
555 } else if (proc->pid == 0) {
556 // child
557 while ((dup2(stdin_pipe[0], STDIN_FILENO) == -1) && (errno == EINTR)) {}
558 close (stdin_pipe[0]);
559 close (stdin_pipe[1]);
560 while ((dup2(stdout_pipe[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
561 close (stdout_pipe[1]);
562 close (stdout_pipe[0]);
563 while ((dup2(stderr_pipe[1], STDERR_FILENO) == -1) && (errno == EINTR)) {}
564 close (stderr_pipe[1]);
565 close (stderr_pipe[0]);
566
567 size_t i;
568 for (i = 0; i < env_size; i++) {
569 setenv (envvars[i], envvals[i], 1);
570 }
571 execvp (file, argv);
572 perror ("exec");
573 r_sys_exit (-1, true);
574 }
575 free (argv);
576
577 // parent
578 close (stdin_pipe[0]);
579 close (stdout_pipe[1]);
580 close (stderr_pipe[1]);
581
582 r_pvector_push (&subprocs, proc);
583
584 r_th_lock_leave (subprocs_mutex);
585
586 return proc;
587 error:
588 free (argv);
589 if (proc && proc->killpipe[0] == -1) {
590 close (proc->killpipe[0]);
591 }
592 if (proc && proc->killpipe[1] == -1) {
593 close (proc->killpipe[1]);
594 }
595 free (proc);
596 if (stderr_pipe[0] == -1) {
597 close (stderr_pipe[0]);
598 }
599 if (stderr_pipe[1] == -1) {
600 close (stderr_pipe[1]);
601 }
602 if (stdout_pipe[0] == -1) {
603 close (stdout_pipe[0]);
604 }
605 if (stdout_pipe[1] == -1) {
606 close (stdout_pipe[1]);
607 }
608 if (stdin_pipe[0] == -1) {
609 close (stdin_pipe[0]);
610 }
611 if (stdin_pipe[1] == -1) {
612 close (stdin_pipe[1]);
613 }
614 r_th_lock_leave (subprocs_mutex);
615 return NULL;
616 }
617
r2r_subprocess_wait(R2RSubprocess * proc,ut64 timeout_ms)618 R_API bool r2r_subprocess_wait(R2RSubprocess *proc, ut64 timeout_ms) {
619 ut64 timeout_abs;
620 if (timeout_ms != UT64_MAX) {
621 timeout_abs = r_time_now_mono () + timeout_ms * R_USEC_PER_MSEC;
622 }
623
624 int r = 0;
625 bool stdout_eof = false;
626 bool stderr_eof = false;
627 bool child_dead = false;
628 while (!stdout_eof || !stderr_eof || !child_dead) {
629 fd_set rfds;
630 FD_ZERO (&rfds);
631 int nfds = 0;
632 if (!stdout_eof) {
633 FD_SET (proc->stdout_fd, &rfds);
634 if (proc->stdout_fd > nfds) {
635 nfds = proc->stdout_fd;
636 }
637 }
638 if (!stderr_eof) {
639 FD_SET (proc->stderr_fd, &rfds);
640 if (proc->stderr_fd > nfds) {
641 nfds = proc->stderr_fd;
642 }
643 }
644 if (!child_dead) {
645 FD_SET (proc->killpipe[0], &rfds);
646 if (proc->killpipe[0] > nfds) {
647 nfds = proc->killpipe[0];
648 }
649 }
650 nfds++;
651
652 struct timeval timeout_s;
653 struct timeval *timeout = NULL;
654 if (timeout_ms != UT64_MAX) {
655 ut64 now = r_time_now_mono ();
656 if (now >= timeout_abs) {
657 break;
658 }
659 ut64 usec_diff = timeout_abs - r_time_now_mono ();
660 timeout_s.tv_sec = usec_diff / R_USEC_PER_SEC;
661 timeout_s.tv_usec = usec_diff % R_USEC_PER_SEC;
662 timeout = &timeout_s;
663 }
664 r = select (nfds, &rfds, NULL, NULL, timeout);
665 if (r < 0) {
666 if (errno == EINTR) {
667 continue;
668 }
669 break;
670 }
671
672 bool timedout = true;
673 if (FD_ISSET (proc->stdout_fd, &rfds)) {
674 timedout = false;
675 char buf[0x500];
676 ssize_t sz = read (proc->stdout_fd, buf, sizeof (buf));
677 if (sz < 0) {
678 perror ("read");
679 } else if (sz == 0) {
680 stdout_eof = true;
681 } else {
682 r_strbuf_append_n (&proc->out, buf, (int)sz);
683 }
684 }
685 if (FD_ISSET (proc->stderr_fd, &rfds)) {
686 timedout = false;
687 char buf[0x500];
688 ssize_t sz = read (proc->stderr_fd, buf, sizeof (buf));
689 if (sz < 0) {
690 perror ("read");
691 continue;
692 } else if (sz == 0) {
693 stderr_eof = true;
694 } else {
695 r_strbuf_append_n (&proc->err, buf, (int)sz);
696 }
697 }
698 if (FD_ISSET (proc->killpipe[0], &rfds)) {
699 timedout = false;
700 child_dead = true;
701 }
702 if (timedout) {
703 break;
704 }
705 }
706 if (r < 0) {
707 perror ("select");
708 }
709 return child_dead;
710 }
711
r2r_subprocess_kill(R2RSubprocess * proc)712 R_API void r2r_subprocess_kill(R2RSubprocess *proc) {
713 kill (proc->pid, SIGKILL);
714 }
715
r2r_subprocess_stdin_write(R2RSubprocess * proc,const ut8 * buf,size_t buf_size)716 R_API void r2r_subprocess_stdin_write(R2RSubprocess *proc, const ut8 *buf, size_t buf_size) {
717 write (proc->stdin_fd, buf, buf_size);
718 close (proc->stdin_fd);
719 proc->stdin_fd = -1;
720 }
721
r2r_subprocess_drain(R2RSubprocess * proc)722 R_API R2RProcessOutput *r2r_subprocess_drain(R2RSubprocess *proc) {
723 r_th_lock_enter (subprocs_mutex);
724 R2RProcessOutput *out = R_NEW (R2RProcessOutput);
725 if (out) {
726 out->out = r_strbuf_drain_nofree (&proc->out);
727 out->err = r_strbuf_drain_nofree (&proc->err);
728 out->ret = proc->ret;
729 out->timeout = false;
730 }
731 r_th_lock_leave (subprocs_mutex);
732 return out;
733 }
734
r2r_subprocess_free(R2RSubprocess * proc)735 R_API void r2r_subprocess_free(R2RSubprocess *proc) {
736 if (!proc) {
737 return;
738 }
739 r_th_lock_enter (subprocs_mutex);
740 r_pvector_remove_data (&subprocs, proc);
741 r_th_lock_leave (subprocs_mutex);
742 r_strbuf_fini (&proc->out);
743 r_strbuf_fini (&proc->err);
744 close (proc->killpipe[0]);
745 close (proc->killpipe[1]);
746 if (proc->stdin_fd != -1) {
747 close (proc->stdin_fd);
748 }
749 close (proc->stdout_fd);
750 close (proc->stderr_fd);
751 free (proc);
752 }
753 #endif
754
r2r_process_output_free(R2RProcessOutput * out)755 R_API void r2r_process_output_free(R2RProcessOutput *out) {
756 if (!out) {
757 return;
758 }
759 free (out->out);
760 free (out->err);
761 free (out);
762 }
763
subprocess_runner(const char * file,const char * args[],size_t args_size,const char * envvars[],const char * envvals[],size_t env_size,ut64 timeout_ms,void * user)764 static R2RProcessOutput *subprocess_runner(const char *file, const char *args[], size_t args_size,
765 const char *envvars[], const char *envvals[], size_t env_size, ut64 timeout_ms, void *user) {
766 R2RSubprocess *proc = r2r_subprocess_start (file, args, args_size, envvars, envvals, env_size);
767 if (!proc) {
768 return NULL;
769 }
770 bool timeout = !r2r_subprocess_wait (proc, timeout_ms);
771 if (timeout) {
772 r2r_subprocess_kill (proc);
773 }
774 R2RProcessOutput *out = r2r_subprocess_drain (proc);
775 if (out) {
776 out->timeout = timeout;
777 }
778 r2r_subprocess_free (proc);
779 return out;
780 }
781
782 #if __WINDOWS__
convert_win_cmds(const char * cmds)783 static char *convert_win_cmds(const char *cmds) {
784 char *r = malloc (strlen (cmds) + 1);
785 if (!r) {
786 return NULL;
787 }
788 char *p = r;
789 while (*cmds) {
790 if (*cmds == '!' || (*cmds == '\"' && cmds[1] == '!')) {
791 // Adjust shell syntax for Windows,
792 // only for lines starting with ! or "!
793 char c;
794 for (; c = *cmds, c; cmds++) {
795 if (c == '\\') {
796 // replace \$ by $
797 c = *++cmds;
798 if (c == '$') {
799 *p++ = '$';
800 } else {
801 *p++ = '\\';
802 *p++ = c;
803 }
804 } else if (c == '$') {
805 // replace ${VARNAME} by %VARNAME%
806 c = *++cmds;
807 if (c == '{') {
808 *p++ = '%';
809 cmds++;
810 for (; c = *cmds, c && c != '}'; *cmds++) {
811 *p++ = c;
812 }
813 if (c) { // must check c to prevent overflow
814 *p++ = '%';
815 }
816 } else {
817 *p++ = '$';
818 *p++ = c;
819 }
820 } else {
821 *p++ = c;
822 if (c == '\n') {
823 cmds++;
824 break;
825 }
826 }
827 }
828 continue;
829 }
830
831 // Nothing to do, just copy the line
832 char *lend = strchr (cmds, '\n');
833 size_t llen;
834 if (lend) {
835 llen = lend - cmds + 1;
836 } else {
837 llen = strlen (cmds);
838 }
839 memcpy (p, cmds, llen);
840 cmds += llen;
841 p += llen;
842 }
843 *p = '\0';
844 return r_str_replace (r, "/dev/null", "nul", true);
845 }
846 #endif
847
run_r2_test(R2RRunConfig * config,ut64 timeout_ms,const char * cmds,RList * files,RList * extra_args,bool load_plugins,R2RCmdRunner runner,void * user)848 static R2RProcessOutput *run_r2_test(R2RRunConfig *config, ut64 timeout_ms, const char *cmds, RList *files, RList *extra_args, bool load_plugins, R2RCmdRunner runner, void *user) {
849 RPVector args;
850 r_pvector_init (&args, NULL);
851 r_pvector_push (&args, "-escr.utf8=0");
852 r_pvector_push (&args, "-escr.color=0");
853 r_pvector_push (&args, "-escr.interactive=0");
854 r_pvector_push (&args, "-N");
855 RListIter *it;
856 void *extra_arg, *file_arg;
857 r_list_foreach (extra_args, it, extra_arg) {
858 r_pvector_push (&args, extra_arg);
859 }
860 r_pvector_push (&args, "-Qc");
861 #if __WINDOWS__
862 char *wcmds = convert_win_cmds (cmds);
863 r_pvector_push (&args, wcmds);
864 #else
865 r_pvector_push (&args, (void *)cmds);
866 #endif
867 r_list_foreach (files, it, file_arg) {
868 r_pvector_push (&args, file_arg);
869 }
870
871 const char *envvars[] = {
872 #if __WINDOWS__
873 "ANSICON",
874 #endif
875 "R2_NOPLUGINS"
876 };
877 const char *envvals[] = {
878 #if __WINDOWS__
879 "1",
880 #endif
881 "1"
882 };
883 #if __WINDOWS__
884 size_t env_size = load_plugins ? 1 : 2;
885 #else
886 size_t env_size = load_plugins ? 0 : 1;
887 #endif
888 R2RProcessOutput *out = runner (config->r2_cmd, args.v.a, r_pvector_len (&args), envvars, envvals, env_size, timeout_ms, user);
889 r_pvector_clear (&args);
890 #if __WINDOWS__
891 free (wcmds);
892 #endif
893 return out;
894 }
895
r2r_run_cmd_test(R2RRunConfig * config,R2RCmdTest * test,R2RCmdRunner runner,void * user)896 R_API R2RProcessOutput *r2r_run_cmd_test(R2RRunConfig *config, R2RCmdTest *test, R2RCmdRunner runner, void *user) {
897 RList *extra_args = test->args.value ? r_str_split_duplist (test->args.value, " ", true) : NULL;
898 RList *files = r_str_split_duplist (test->file.value, "\n", true);
899 RListIter *it;
900 RListIter *tmpit;
901 char *token;
902 r_list_foreach_safe (extra_args, it, tmpit, token) {
903 if (!*token) {
904 r_list_delete (extra_args, it);
905 }
906 }
907 r_list_foreach_safe (files, it, tmpit, token) {
908 if (!*token) {
909 r_list_delete (files, it);
910 }
911 }
912 if (r_list_empty (files)) {
913 if (!files) {
914 files = r_list_new ();
915 } else {
916 files->free = NULL;
917 }
918 r_list_push (files, "-");
919 }
920 ut64 timeout_ms = test->timeout.set? test->timeout.value * 1000: config->timeout_ms;
921 R2RProcessOutput *out = run_r2_test (config, timeout_ms, test->cmds.value, files, extra_args, test->load_plugins, runner, user);
922 r_list_free (extra_args);
923 r_list_free (files);
924 return out;
925 }
926
r2r_check_cmd_test(R2RProcessOutput * out,R2RCmdTest * test)927 R_API bool r2r_check_cmd_test(R2RProcessOutput *out, R2RCmdTest *test) {
928 if (!out || out->ret != 0 || !out->out || !out->err || out->timeout) {
929 return false;
930 }
931 const char *expect_out = test->expect.value;
932 if (expect_out && strcmp (out->out, expect_out) != 0) {
933 return false;
934 }
935 const char *expect_err = test->expect_err.value;
936 if (expect_err && strcmp (out->err, expect_err) != 0) {
937 return false;
938 }
939 const char *regexp_out = test->regexp_out.value;
940 if (regexp_out && !r_regex_match (regexp_out, "e", out->out)) {
941 return false;
942 }
943 const char *regexp_err = test->regexp_err.value;
944 if (regexp_err && !r_regex_match (regexp_err, "e", out->err)) {
945 return false;
946 }
947 return true;
948 }
949
950 #define JQ_CMD "jq"
951
r2r_check_jq_available(void)952 R_API bool r2r_check_jq_available(void) {
953 const char *args[] = {"."};
954 const char *invalid_json = "this is not json lol";
955 R2RSubprocess *proc = r2r_subprocess_start (JQ_CMD, args, 1, NULL, NULL, 0);
956 if (proc) {
957 r2r_subprocess_stdin_write (proc, (const ut8 *)invalid_json, strlen (invalid_json));
958 r2r_subprocess_wait (proc, UT64_MAX);
959 }
960 bool invalid_detected = proc && proc->ret != 0;
961 r2r_subprocess_free (proc);
962
963 const char *valid_json = "{\"this is\":\"valid json\",\"lol\":true}";
964 proc = r2r_subprocess_start (JQ_CMD, args, 1, NULL, NULL, 0);
965 if (proc) {
966 r2r_subprocess_stdin_write (proc, (const ut8 *)valid_json, strlen (valid_json));
967 r2r_subprocess_wait (proc, UT64_MAX);
968 }
969 bool valid_detected = proc && proc->ret == 0;
970 r2r_subprocess_free (proc);
971
972 return invalid_detected && valid_detected;
973 }
974
r2r_run_json_test(R2RRunConfig * config,R2RJsonTest * test,R2RCmdRunner runner,void * user)975 R_API R2RProcessOutput *r2r_run_json_test(R2RRunConfig *config, R2RJsonTest *test, R2RCmdRunner runner, void *user) {
976 RList *files = r_list_new ();
977 r_list_push (files, (void *)config->json_test_file);
978 R2RProcessOutput *ret = run_r2_test (config, config->timeout_ms, test->cmd, files, NULL, test->load_plugins, runner, user);
979 r_list_free (files);
980 return ret;
981 }
982
r2r_check_json_test(R2RProcessOutput * out,R2RJsonTest * test)983 R_API bool r2r_check_json_test(R2RProcessOutput *out, R2RJsonTest *test) {
984 if (!out || out->ret != 0 || !out->out || !out->err || out->timeout) {
985 return false;
986 }
987 const char *args[] = {"."};
988 R2RSubprocess *proc = r2r_subprocess_start (JQ_CMD, args, 1, NULL, NULL, 0);
989 r2r_subprocess_stdin_write (proc, (const ut8 *)out->out, strlen (out->out));
990 r2r_subprocess_wait (proc, UT64_MAX);
991 bool ret = proc->ret == 0;
992 r2r_subprocess_free (proc);
993 return ret;
994 }
995
r2r_run_asm_test(R2RRunConfig * config,R2RAsmTest * test)996 R_API R2RAsmTestOutput *r2r_run_asm_test(R2RRunConfig *config, R2RAsmTest *test) {
997 R2RAsmTestOutput *out = R_NEW0 (R2RAsmTestOutput);
998 if (!out) {
999 return NULL;
1000 }
1001
1002 RPVector args;
1003 r_pvector_init (&args, NULL);
1004
1005 if (test->arch) {
1006 r_pvector_push (&args, "-a");
1007 r_pvector_push (&args, (void *)test->arch);
1008 }
1009
1010 if (test->cpu) {
1011 r_pvector_push (&args, "-c");
1012 r_pvector_push (&args, (void *)test->cpu);
1013 }
1014
1015 char bits[0x20];
1016 if (test->bits) {
1017 snprintf (bits, sizeof (bits), "%d", test->bits);
1018 r_pvector_push (&args, "-b");
1019 r_pvector_push (&args, bits);
1020 }
1021
1022 if (test->mode & R2R_ASM_TEST_MODE_BIG_ENDIAN) {
1023 r_pvector_push (&args, "-e");
1024 }
1025
1026 char offset[0x20];
1027 if (test->offset) {
1028 r_snprintf (offset, sizeof (offset), "0x%"PFMT64x, test->offset);
1029 r_pvector_push (&args, "-o");
1030 r_pvector_push (&args, offset);
1031 }
1032
1033 RStrBuf cmd_buf;
1034 r_strbuf_init (&cmd_buf);
1035 if (test->mode & R2R_ASM_TEST_MODE_ASSEMBLE) {
1036 r_pvector_push (&args, test->disasm);
1037 R2RSubprocess *proc = r2r_subprocess_start (config->rasm2_cmd, args.v.a, r_pvector_len (&args), NULL, NULL, 0);
1038 if (!r2r_subprocess_wait (proc, config->timeout_ms)) {
1039 r2r_subprocess_kill (proc);
1040 out->as_timeout = true;
1041 goto rip;
1042 }
1043 if (proc->ret != 0) {
1044 goto rip;
1045 }
1046 char *hex = r_strbuf_get (&proc->out);
1047 size_t hexlen = strlen (hex);
1048 if (!hexlen) {
1049 goto rip;
1050 }
1051 ut8 *bytes = malloc (hexlen);
1052 int byteslen = r_hex_str2bin (hex, bytes);
1053 if (byteslen <= 0) {
1054 free (bytes);
1055 goto rip;
1056 }
1057 out->bytes = bytes;
1058 out->bytes_size = (size_t)byteslen;
1059 rip:
1060 r_pvector_pop (&args);
1061 r2r_subprocess_free (proc);
1062 }
1063 if (test->mode & R2R_ASM_TEST_MODE_DISASSEMBLE) {
1064 char *hex = r_hex_bin2strdup (test->bytes, test->bytes_size);
1065 if (!hex) {
1066 goto beach;
1067 }
1068 r_pvector_push (&args, "-d");
1069 r_pvector_push (&args, hex);
1070 R2RSubprocess *proc = r2r_subprocess_start (config->rasm2_cmd, args.v.a, r_pvector_len (&args), NULL, NULL, 0);
1071 if (!r2r_subprocess_wait (proc, config->timeout_ms)) {
1072 r2r_subprocess_kill (proc);
1073 out->disas_timeout = true;
1074 goto ship;
1075 }
1076 if (proc->ret != 0) {
1077 goto ship;
1078 }
1079 char *disasm = r_strbuf_drain_nofree (&proc->out);
1080 r_str_trim (disasm);
1081 out->disasm = disasm;
1082 ship:
1083 free (hex);
1084 r_pvector_pop (&args);
1085 r_pvector_pop (&args);
1086 r2r_subprocess_free (proc);
1087 }
1088
1089 beach:
1090 r_pvector_clear (&args);
1091 r_strbuf_fini (&cmd_buf);
1092 return out;
1093 }
1094
r2r_check_asm_test(R2RAsmTestOutput * out,R2RAsmTest * test)1095 R_API bool r2r_check_asm_test(R2RAsmTestOutput *out, R2RAsmTest *test) {
1096 if (!out) {
1097 return false;
1098 }
1099 if (test->mode & R2R_ASM_TEST_MODE_ASSEMBLE) {
1100 if (!out->bytes || !test->bytes || out->bytes_size != test->bytes_size || out->as_timeout) {
1101 return false;
1102 }
1103 if (memcmp (out->bytes, test->bytes, test->bytes_size) != 0) {
1104 return false;
1105 }
1106 }
1107 if (test->mode & R2R_ASM_TEST_MODE_DISASSEMBLE) {
1108 if (!out->disasm || !test->disasm || out->as_timeout) {
1109 return false;
1110 }
1111 if (strcmp (out->disasm, test->disasm) != 0) {
1112 return false;
1113 }
1114 }
1115 return true;
1116 }
1117
r2r_asm_test_output_free(R2RAsmTestOutput * out)1118 R_API void r2r_asm_test_output_free(R2RAsmTestOutput *out) {
1119 if (!out) {
1120 return;
1121 }
1122 free (out->disasm);
1123 free (out->bytes);
1124 free (out);
1125 }
1126
r2r_run_fuzz_test(R2RRunConfig * config,R2RFuzzTest * test,R2RCmdRunner runner,void * user)1127 R_API R2RProcessOutput *r2r_run_fuzz_test(R2RRunConfig *config, R2RFuzzTest *test, R2RCmdRunner runner, void *user) {
1128 const char *cmd = "aaa";
1129 RList *files = r_list_new ();
1130 r_list_push (files, test->file);
1131 #if ASAN
1132 if (r_str_endswith (test->file, "/swift_read")) {
1133 cmd = "?F";
1134 }
1135 #endif
1136 R2RProcessOutput *ret = run_r2_test (config, config->timeout_ms, cmd, files, NULL, false, runner, user);
1137 r_list_free (files);
1138 return ret;
1139 }
1140
r2r_check_fuzz_test(R2RProcessOutput * out)1141 R_API bool r2r_check_fuzz_test(R2RProcessOutput *out) {
1142 return out && out->ret == 0 && out->out && out->err && !out->timeout;
1143 }
1144
r2r_test_name(R2RTest * test)1145 R_API char *r2r_test_name(R2RTest *test) {
1146 switch (test->type) {
1147 case R2R_TEST_TYPE_CMD:
1148 if (test->cmd_test->name.value) {
1149 return strdup (test->cmd_test->name.value);
1150 }
1151 return strdup ("<unnamed>");
1152 case R2R_TEST_TYPE_ASM:
1153 return r_str_newf ("<asm> %s", r_str_get (test->asm_test->disasm));
1154 case R2R_TEST_TYPE_JSON:
1155 return r_str_newf ("<json> %s", r_str_get (test->json_test->cmd));
1156 case R2R_TEST_TYPE_FUZZ:
1157 return r_str_newf ("<fuzz> %s", test->fuzz_test->file);
1158 }
1159 return NULL;
1160 }
1161
r2r_test_broken(R2RTest * test)1162 R_API bool r2r_test_broken(R2RTest *test) {
1163 switch (test->type) {
1164 case R2R_TEST_TYPE_CMD:
1165 return test->cmd_test->broken.value;
1166 case R2R_TEST_TYPE_ASM:
1167 return test->asm_test->mode & R2R_ASM_TEST_MODE_BROKEN ? true : false;
1168 case R2R_TEST_TYPE_JSON:
1169 return test->json_test->broken;
1170 case R2R_TEST_TYPE_FUZZ:
1171 return false;
1172 }
1173 return false;
1174 }
1175
r2r_run_test(R2RRunConfig * config,R2RTest * test)1176 R_API R2RTestResultInfo *r2r_run_test(R2RRunConfig *config, R2RTest *test) {
1177 R2RTestResultInfo *ret = R_NEW0 (R2RTestResultInfo);
1178 if (!ret) {
1179 return NULL;
1180 }
1181 ret->test = test;
1182 bool success = false;
1183 ut64 start_time = r_time_now_mono ();
1184 switch (test->type) {
1185 case R2R_TEST_TYPE_CMD: {
1186 R2RCmdTest *cmd_test = test->cmd_test;
1187 R2RProcessOutput *out = r2r_run_cmd_test (config, cmd_test, subprocess_runner, NULL);
1188 success = r2r_check_cmd_test (out, cmd_test);
1189 ret->proc_out = out;
1190 ret->timeout = out && out->timeout;
1191 ret->run_failed = !out;
1192 break;
1193 }
1194 case R2R_TEST_TYPE_ASM: {
1195 R2RAsmTest *asm_test = test->asm_test;
1196 R2RAsmTestOutput *out = r2r_run_asm_test (config, asm_test);
1197 success = r2r_check_asm_test (out, asm_test);
1198 ret->asm_out = out;
1199 ret->timeout = out->as_timeout || out->disas_timeout;
1200 ret->run_failed = !out;
1201 break;
1202 }
1203 case R2R_TEST_TYPE_JSON: {
1204 R2RJsonTest *json_test = test->json_test;
1205 R2RProcessOutput *out = r2r_run_json_test (config, json_test, subprocess_runner, NULL);
1206 success = r2r_check_json_test (out, json_test);
1207 ret->proc_out = out;
1208 ret->timeout = out->timeout;
1209 ret->run_failed = !out;
1210 break;
1211 }
1212 case R2R_TEST_TYPE_FUZZ: {
1213 R2RFuzzTest *fuzz_test = test->fuzz_test;
1214 R2RProcessOutput *out = r2r_run_fuzz_test (config, fuzz_test, subprocess_runner, NULL);
1215 success = r2r_check_fuzz_test (out);
1216 ret->proc_out = out;
1217 ret->timeout = out->timeout;
1218 ret->run_failed = !out;
1219 }
1220 }
1221 ret->time_elapsed = r_time_now_mono () - start_time;
1222 bool broken = r2r_test_broken (test);
1223 #if ASAN
1224 # if !R2_ASSERT_STDOUT
1225 # error R2_ASSERT_STDOUT undefined or 0
1226 # endif
1227 R2RProcessOutput *out = ret->proc_out;
1228 if (!success && test->type == R2R_TEST_TYPE_CMD && strstr (test->path, "/dbg")
1229 && (!out->out ||
1230 (!strstr (out->out, "WARNING:") && !strstr (out->out, "ERROR:") && !strstr (out->out, "FATAL:")))
1231 && (!out->err ||
1232 (!strstr (out->err, "Sanitizer") && !strstr (out->err, "runtime error:")))) {
1233 broken = true;
1234 }
1235 #endif
1236 if (!success) {
1237 ret->result = broken ? R2R_TEST_RESULT_BROKEN : R2R_TEST_RESULT_FAILED;
1238 } else {
1239 ret->result = broken ? R2R_TEST_RESULT_FIXED : R2R_TEST_RESULT_OK;
1240 }
1241 return ret;
1242 }
1243
r2r_test_result_info_free(R2RTestResultInfo * result)1244 R_API void r2r_test_result_info_free(R2RTestResultInfo *result) {
1245 if (!result) {
1246 return;
1247 }
1248 if (result->test) {
1249 switch (result->test->type) {
1250 case R2R_TEST_TYPE_CMD:
1251 case R2R_TEST_TYPE_JSON:
1252 case R2R_TEST_TYPE_FUZZ:
1253 r2r_process_output_free (result->proc_out);
1254 break;
1255 case R2R_TEST_TYPE_ASM:
1256 r2r_asm_test_output_free (result->asm_out);
1257 break;
1258 }
1259 }
1260 free (result);
1261 }
1262