Compare commits

...
19 Commits
Author SHA1 Message Date
f4alt 7b60322801 check fgets return val 2024-04-10 13:00:29 -04:00
f4alt b29af826f0 Werror=format-security 2024-04-10 08:57:48 -04:00
f4alt ffce50b77c subprocess.h handles this wait 2024-04-10 01:25:27 -04:00
f4alt b273dc488d missed these from fa786a7f6 2024-04-10 01:01:00 -04:00
f4alt afb8c9193b fix build errors (Linux)
correct pid querying and missed #ifdef changes
2024-04-10 00:51:36 -04:00
f4alt 64cfc9ffd9 wip sync
subprocess is almost there. Next major hurdle is Windows is throwing an
invalid handle specified warning on mged's exit. Syncing current
progress and testing on Linux to see if valgrind can be of any help.
2024-04-10 00:09:56 -04:00
f4alt 4e2a90e610 modify subprocess.h to allow closing of stdin
We rely on closing stdin to signal the conclusion of commands for rt.
Ideally, we'll get these changes pr'd upstream after settling our use
case and the 'right way' to do this.
2024-04-10 00:07:53 -04:00
f4alt fa786a7f66 bu_process_wait needs the process by reference
small qol, but makes quite the difference. Since we handle the free
inside 'wait', we need to be able to null out the pointer to protect
against future (albeit incorrect) calls after the function.
2024-04-10 00:07:12 -04:00
f4alt 5cf286398d bu_process_alive() 2024-04-04 12:46:02 -04:00
f4alt 4a8140db46 add read test with nothing on output to make sure we dont hang 2024-04-04 12:41:38 -04:00
f4alt 35dbcb49a1 test 'alive' after termination 2024-04-04 12:23:49 -04:00
f4alt 9cbb25d936 beef up process tests even more 2024-03-27 16:11:15 -04:00
f4alt 4a6cc740fe pull in current upstream non-blocking async work
https://github.com/sheredom/subprocess.h/commit/220b98b91bc9de5b5e6482d47ee83c6d08a1ee50
2024-03-25 16:54:34 -04:00
f4alt 5d1940ca86 Merge remote-tracking branch 'origin/main' into subprocess 2024-03-20 16:54:20 -04:00
f4alt 025158d317 track files: subprocess.h and legal info 2024-03-12 14:08:00 -04:00
f4alt 50c70abdb6 cleanup some of the process.h descriptions 2024-03-12 14:08:00 -04:00
f4alt 6f2f7947e2 use 'all' target for new bu_process tests 2024-03-12 14:08:00 -04:00
f4alt e2d26994d3 first pass at slotting in subprocess.h
look at using https://github.com/sheredom/subprocess.h for our
subprocess heavy lifting.
2024-03-12 14:08:00 -04:00
f4alt c83308339b up our bu_process tests 2024-03-12 14:08:00 -04:00
16 changed files with 1944 additions and 423 deletions
+1
View File
@@ -66,6 +66,7 @@ set(embedded_licenses
stb_truetype.txt
STIX_Font_License_2010.txt
strnstr.txt
subprocess.txt
tcl.txt
tcllib.txt
tiny_gltf.txt
+26
View File
@@ -0,0 +1,26 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
file:/src/libbu/subprocess.h
+8 -8
View File
@@ -59,7 +59,7 @@ typedef enum {
} bu_process_io_t;
/**
* Open and return a FILE pointer associated with the specified file
* Return a FILE pointer associated with the specified file
* descriptor for input (0), output (1), or error (2) respectively.
*
* Input will be opened write, output and error will be opened
@@ -95,9 +95,7 @@ BU_EXPORT int bu_process_fileno(struct bu_process *pinfo, bu_process_io_t d);
/**
* Return the pid of the subprocess.
*
* FIXME: seemingly redundant or combinable with bu_process_id()
* (perhaps make NULL be equivalent to the current process).
* Or, if NULL is supplied, return the current processes id
*/
BU_EXPORT int bu_process_pid(struct bu_process *pinfo);
@@ -140,6 +138,7 @@ BU_EXPORT extern int bu_process_read(char *buff, int *count, struct bu_process *
* exposed to parameters not relevant to them.
*/
BU_EXPORT extern void bu_process_exec(struct bu_process **info, const char *cmd, int argc, const char **argv, int out_eql_err, int hide_window);
BU_EXPORT extern void bu_process_exec_(struct bu_process **info, const char *cmd, int argc, const char **argv, int options);
/**
@@ -149,8 +148,9 @@ BU_EXPORT extern void bu_process_exec(struct bu_process **info, const char *cmd,
* FIXME: 'aborted' argument may be unnecessary (could make function
* provide return value of the process waited for). wtime
* undocumented.
* FIXME: wtime is no longer used on any platforms.
*/
BU_EXPORT extern int bu_process_wait(int *aborted, struct bu_process *pinfo, int wtime);
BU_EXPORT extern int bu_process_wait(int *aborted, struct bu_process **pinfo, int wtime);
/**
* @brief detect whether or not a program is being run in interactive mode
@@ -169,11 +169,11 @@ BU_EXPORT extern int bu_process_pending(int fd);
/**
* @brief determine whether there is data pending on fd
* @brief whether process is still alive and running
*
* 1 if there is data, else 0
* returns 1 if alive, else 0
*/
BU_EXPORT extern int bu_process_pending(int fd);
BU_EXPORT extern int bu_process_alive(struct bu_process *pinfo);
/** @} */
+1
View File
@@ -194,6 +194,7 @@ set(bu_ignore_files
mime.cmake
parallel.h
process.h
subprocess.h
tests/semchk.cpp
sha1.h
vfont.h
+98 -344
View File
@@ -38,6 +38,7 @@
#include "bu/str.h"
#include "bu/vls.h"
#include "./process.h"
#include "./subprocess.h"
#if !defined(HAVE_DECL_WAIT) && !defined(wait) && !defined(_WINSOCKAPI_)
extern pid_t wait(int *);
@@ -56,75 +57,39 @@ bu_process_id(void)
struct bu_process {
struct bu_list l;
struct subprocess_s* subprocess_p;
const char *cmd;
int argc;
const char **argv;
FILE *fp_in;
FILE *fp_out;
FILE *fp_err;
int fd_in;
int fd_out;
int fd_err;
#if defined(_WIN32) && !defined(__CYGWIN__)
HANDLE hProcess;
DWORD pid;
#else
int pid;
#endif
int aborted;
int aborted; // TODO: do we still need this?
};
void
bu_process_close(struct bu_process *pinfo, bu_process_io_t d)
bu_process_close(struct bu_process *pinfo, bu_process_io_t stream_type)
{
if (!pinfo)
return;
if (stream_type == BU_PROCESS_STDIN)
(void)subprocess_close_stdin(pinfo->subprocess_p);
if (d == BU_PROCESS_STDIN) {
if (!pinfo->fp_in)
return;
(void)fclose(pinfo->fp_in);
pinfo->fp_in = NULL;
return;
}
if (d == BU_PROCESS_STDOUT) {
if (!pinfo->fp_out)
return;
(void)fclose(pinfo->fp_out);
pinfo->fp_out = NULL;
return;
}
if (d == BU_PROCESS_STDERR) {
if (!pinfo->fp_err)
return;
(void)fclose(pinfo->fp_err);
pinfo->fp_err = NULL;
return;
}
/* we're not actually opening anything with bu_process_open */
}
FILE *
bu_process_open(struct bu_process *pinfo, bu_process_io_t d)
bu_process_open(struct bu_process *pinfo, bu_process_io_t stream_type)
{
if (!pinfo)
return NULL;
bu_process_close(pinfo, d);
if (d == BU_PROCESS_STDIN) {
pinfo->fp_in = fdopen(pinfo->fd_in, "wb");
return pinfo->fp_in;
}
if (d == BU_PROCESS_STDOUT) {
pinfo->fp_out = fdopen(pinfo->fd_out, "rb");
return pinfo->fp_out;
}
if (d == BU_PROCESS_STDERR) {
pinfo->fp_err = fdopen(pinfo->fd_err, "rb");
return pinfo->fp_err;
switch (stream_type)
{
case BU_PROCESS_STDIN:
return subprocess_stdin(pinfo->subprocess_p);
case BU_PROCESS_STDOUT:
return subprocess_stdout(pinfo->subprocess_p);
case BU_PROCESS_STDERR:
return subprocess_stderr(pinfo->subprocess_p);
}
return NULL;
@@ -132,17 +97,20 @@ bu_process_open(struct bu_process *pinfo, bu_process_io_t d)
int
bu_process_fileno(struct bu_process *pinfo, bu_process_io_t d)
bu_process_fileno(struct bu_process *pinfo, bu_process_io_t stream_type)
{
if (!pinfo)
if (!pinfo || !pinfo->subprocess_p)
return -1;
if (d == BU_PROCESS_STDIN)
return pinfo->fd_in;
if (d == BU_PROCESS_STDOUT)
return pinfo->fd_out;
if (d == BU_PROCESS_STDERR)
return pinfo->fd_err;
switch (stream_type)
{
case BU_PROCESS_STDIN:
return fileno(subprocess_stdin(pinfo->subprocess_p));
case BU_PROCESS_STDOUT:
return fileno(subprocess_stdout(pinfo->subprocess_p));
case BU_PROCESS_STDERR:
return fileno(subprocess_stderr(pinfo->subprocess_p));
}
return -1;
}
@@ -151,9 +119,12 @@ bu_process_fileno(struct bu_process *pinfo, bu_process_io_t d)
int
bu_process_pid(struct bu_process *pinfo)
{
if (!pinfo)
return -1;
return (int)pinfo->pid;
if (!pinfo) {
/* if no process specified, return the current processes ID */
return bu_process_id();
}
return subprocess_pid(pinfo->subprocess_p);
}
@@ -173,25 +144,27 @@ bu_process_args(const char **cmd, const char * const **argv, struct bu_process *
int
bu_process_read(char *buff, int *count, struct bu_process *pinfo, bu_process_io_t d, int n)
bu_process_read(char *buff, int *count, struct bu_process *pinfo, bu_process_io_t stream_type, int n)
{
int ret = 1;
if (!pinfo || !buff || !n || !count)
return -1;
if (d == BU_PROCESS_STDOUT) {
(*count) = read((int)pinfo->fd_out, buff, n);
if ((*count) <= 0) {
ret = -1;
}
}
if (d == BU_PROCESS_STDERR) {
(*count) = read((int)pinfo->fd_err, buff, n);
if ((*count) <= 0) {
ret = -1;
}
switch (stream_type)
{
case BU_PROCESS_STDOUT:
*count = subprocess_read_stdout(pinfo->subprocess_p, buff, n);
break;
case BU_PROCESS_STDERR:
*count = subprocess_read_stderr(pinfo->subprocess_p, buff, n);
break;
default: // invalid stream_type
return -1;
}
if ((*count) <= 0)
ret = -1;
/* sanity clamping */
if ((*count) < 0) {
perror("READ ERROR");
@@ -205,34 +178,16 @@ bu_process_read(char *buff, int *count, struct bu_process *pinfo, bu_process_io_
void
bu_process_exec(
struct bu_process **p, const char *cmd, int argc, const char **argv, int out_eql_err,
#ifdef _WIN32
int hide_window
#else
int UNUSED(hide_window)
#endif
)
bu_process_exec_(struct bu_process **p, const char *cmd, int argc, const char **argv, int options)
{
int pret = 0;
int ac = argc;
#ifdef HAVE_UNISTD_H
int pid;
int pipe_in[2];
int pipe_out[2];
int pipe_err[2];
const char **av = NULL;
if (!p || !cmd)
return;
BU_GET(*p, struct bu_process);
(*p)->fp_in = NULL;
(*p)->fp_out = NULL;
(*p)->fp_err = NULL;
(*p)->fd_in = -1;
(*p)->fd_out = -1;
(*p)->fd_err = -1;
BU_GET((*p)->subprocess_p, struct subprocess_s);
int ac = argc;
const char **av = NULL;
av = (const char **)bu_calloc(argc+2, sizeof(char *), "argv array");
if (!argc || !BU_STR_EQUAL(cmd, argv[0])) {
@@ -261,272 +216,62 @@ bu_process_exec(
}
(*p)->argv[ac] = (char *)NULL;
pret = pipe(pipe_in);
if (pret < 0) {
perror("pipe");
}
pret = pipe(pipe_out);
if (pret < 0) {
perror("pipe");
}
pret = pipe(pipe_err);
if (pret < 0) {
perror("pipe");
}
/* fork + exec */
if ((pid = fork()) == 0) {
int d1, d2, d3;
/* make this a process group leader */
setpgid(0, 0);
/* Redirect stdin and stderr */
(void)close(BU_PROCESS_STDIN);
d1 = dup(pipe_in[0]);
if (d1 < 0) {
perror("dup");
}
(void)close(BU_PROCESS_STDOUT);
d2 = dup(pipe_out[1]);
if (d2 < 0) {
perror("dup");
}
(void)close(BU_PROCESS_STDERR);
d3 = dup(pipe_err[1]);
if (d3 < 0) {
perror("dup");
}
/* close pipes */
(void)close(pipe_in[0]);
(void)close(pipe_in[1]);
(void)close(pipe_out[0]);
(void)close(pipe_out[1]);
(void)close(pipe_err[0]);
(void)close(pipe_err[1]);
// TODO - should we be doing this for more than 20? See
// https://docs.fedoraproject.org/en-US/Fedora_Security_Team/1/html/Defensive_Coding/sect-Defensive_Coding-Tasks-Descriptors-Child_Processes.html
for (int i = 3; i < 20; i++) {
(void)close(i);
}
(void)execvp(cmd, (char * const*)av);
perror(cmd);
#if 0
// TODO - do we need to close the dup handles?
fflush(NULL);
close(d1);
close(d2);
close(d3);
#endif
exit(16);
}
(void)close(pipe_in[0]);
(void)close(pipe_out[1]);
(void)close(pipe_err[1]);
/* Save necessary information for parental process manipulation */
(*p)->fd_in = pipe_in[1];
if (out_eql_err) {
(*p)->fd_out = pipe_err[0];
} else {
(*p)->fd_out = pipe_out[0];
}
(*p)->fd_err = pipe_err[0];
(*p)->pid = pid;
#else
struct bu_vls cp_cmd = BU_VLS_INIT_ZERO;
HANDLE pipe_in[2], pipe_inDup;
HANDLE pipe_out[2], pipe_outDup;
HANDLE pipe_err[2], pipe_errDup;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
SECURITY_ATTRIBUTES sa = {0};
if (!cmd || !argc || !argv)
return;
BU_GET(*p, struct bu_process);
(*p)->fp_in = NULL;
(*p)->fp_out = NULL;
(*p)->fp_err = NULL;
(*p)->fd_in = -1;
(*p)->fd_out = -1;
(*p)->fd_err = -1;
(*p)->cmd = bu_strdup(cmd);
(*p)->argc = argc;
(*p)->argv = (const char **)bu_calloc(argc+1, sizeof(char *), "bu_process argv cpy");
for (int i = 0; i < argc; i++) {
(*p)->argv[i] = bu_strdup(argv[i]);
}
(*p)->argv[ac] = (char *)NULL;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
/* Create a pipe for the child process's STDOUT. */
CreatePipe(&pipe_out[0], &pipe_out[1], &sa, 0);
/* Create noninheritable read handle and close the inheritable read handle. */
DuplicateHandle(GetCurrentProcess(), pipe_out[0],
GetCurrentProcess(), &pipe_outDup ,
0, FALSE,
DUPLICATE_SAME_ACCESS);
CloseHandle(pipe_out[0]);
/* Create a pipe for the child process's STDERR. */
CreatePipe(&pipe_err[0], &pipe_err[1], &sa, 0);
/* Create noninheritable read handle and close the inheritable read handle. */
DuplicateHandle(GetCurrentProcess(), pipe_err[0],
GetCurrentProcess(), &pipe_errDup ,
0, FALSE,
DUPLICATE_SAME_ACCESS);
CloseHandle(pipe_err[0]);
/* Create a pipe for the child process's STDIN. */
CreatePipe(&pipe_in[0], &pipe_in[1], &sa, 0);
/* Duplicate the write handle to the pipe so it is not inherited. */
DuplicateHandle(GetCurrentProcess(), pipe_in[1],
GetCurrentProcess(), &pipe_inDup,
0, FALSE, /* not inherited */
DUPLICATE_SAME_ACCESS);
CloseHandle(pipe_in[1]);
si.cb = sizeof(STARTUPINFO);
si.lpReserved = NULL;
si.lpReserved2 = NULL;
si.cbReserved2 = 0;
si.lpDesktop = NULL;
if (hide_window) {
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
} else {
si.dwFlags = STARTF_USESTDHANDLES;
}
si.hStdInput = pipe_in[0];
if (out_eql_err) {
si.hStdOutput = pipe_err[1];
} else {
si.hStdOutput = pipe_out[1];
}
si.hStdError = pipe_err[1];
/* Create_Process uses a string, not a char array */
for (int i = 0; i < argc; i++) {
/* Quote all path names or arguments with spaces for CreateProcess */
if (strstr(argv[i], " ") || bu_file_exists(argv[i], NULL)) {
bu_vls_printf(&cp_cmd, "\"%s\" ", argv[i]);
} else {
bu_vls_printf(&cp_cmd, "%s ", argv[i]);
}
}
CreateProcess(NULL, bu_vls_addr(&cp_cmd), NULL, NULL, TRUE,
DETACHED_PROCESS, NULL, NULL,
&si, &pi);
bu_vls_free(&cp_cmd);
CloseHandle(pipe_in[0]);
CloseHandle(pipe_out[1]);
CloseHandle(pipe_err[1]);
/* Save necessary information for parental process manipulation.
* Switching from HANDLE to file descriptor so the rest of the code can be
* consistent - see
* https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/open-osfhandle
*/
(*p)->fd_in = _open_osfhandle((intptr_t)pipe_inDup, 0);
if (out_eql_err) {
(*p)->fd_out = _open_osfhandle((intptr_t)pipe_errDup, 0);
} else {
(*p)->fd_out = _open_osfhandle((intptr_t)pipe_outDup, 0);
}
(*p)->fd_err = _open_osfhandle((intptr_t)pipe_errDup, 0);
(*p)->hProcess = pi.hProcess;
(*p)->pid = pi.dwProcessId;
(*p)->aborted = 0;
#endif
// TODO: return whether process was created successfully?
subprocess_create(argv, options, (*p)->subprocess_p);
}
void
bu_process_exec(struct bu_process **p, const char *cmd, int argc, const char **argv, int out_eql_err, int hide_window)
{
/* match out_eql_err and hide_window flags to subprocess options */
int options = 0;
if (out_eql_err) options |= subprocess_option_combined_stdout_stderr;
if (hide_window) options |= subprocess_option_no_window;
bu_process_exec_(p, cmd, argc, argv, options);
}
int
bu_process_wait(
int *aborted, struct bu_process *pinfo,
#ifndef _WIN32
int UNUSED(wtime)
#else
int wtime
#endif
)
bu_process_wait(int *aborted, struct bu_process **pinfo, int UNUSED(wtime))
{
int rc = 0;
#ifndef _WIN32
int rpid;
int retcode = 0;
if (!pinfo)
return -1;
close(pinfo->fd_in);
close(pinfo->fd_out);
close(pinfo->fd_err);
while ((rpid = wait(&retcode)) != pinfo->pid && rpid != -1) {
if ((*pinfo)->subprocess_p && subprocess_join((*pinfo)->subprocess_p, &rc)) {
/* unsuccessful join, forcefully destroy */
(void)subprocess_destroy((*pinfo)->subprocess_p);
}
rc = retcode;
// NOTE: if we want to maintain an 'aborted' var, we have to do some platform specific gunk
#ifndef _WIN32
if (rc) {
pinfo->aborted = 1;
(*pinfo)->aborted = 1;
}
#else
DWORD retcode = 0;
if (!pinfo)
return -1;
/* wait for the forked process */
if (wtime > 0) {
WaitForSingleObject(pinfo->hProcess, wtime);
} else {
WaitForSingleObject(pinfo->hProcess, INFINITE);
if (GetLastError() == ERROR_PROCESS_ABORTED || rc == BU_MSVC_ABORT_EXIT) {
(*pinfo)->aborted = 1;
}
GetExitCodeProcess(pinfo->hProcess, &retcode);
if (GetLastError() == ERROR_PROCESS_ABORTED || retcode == BU_MSVC_ABORT_EXIT) {
pinfo->aborted = 1;
}
/* may be useful to try pr_wait_status() here */
rc = (int)retcode;
#endif
if (aborted) {
(*aborted) = pinfo->aborted;
if (aborted) {
(*aborted) = (*pinfo)->aborted;
}
/* Clean up */
bu_process_close(pinfo, BU_PROCESS_STDOUT);
bu_process_close(pinfo, BU_PROCESS_STDERR);
bu_process_close(*pinfo, BU_PROCESS_STDOUT);
bu_process_close(*pinfo, BU_PROCESS_STDERR);
/* Free copy of exec args */
bu_free((void *)pinfo->cmd, "pinfo cmd copy");
/* Free copy of exec args and struct allocs */
bu_free((void *)(*pinfo)->cmd, "pinfo cmd copy");
if (pinfo->argv) {
for (int i = 0; i < pinfo->argc; i++) {
bu_free((void *)pinfo->argv[i], "pinfo argv member");
if ((*pinfo)->argv) {
for (int i = 0; i < (*pinfo)->argc; i++) {
bu_free((void *)(*pinfo)->argv[i], "pinfo argv member");
}
bu_free((void *)pinfo->argv, "pinfo argv array");
bu_free((void *)(*pinfo)->argv, "pinfo argv array");
}
BU_PUT(pinfo, struct bu_process);
BU_PUT((*pinfo)->subprocess_p, struct subprocess_t);
BU_PUT(*pinfo, struct bu_process);
return rc;
}
@@ -569,6 +314,15 @@ bu_process_pending(int fd)
return result > 0 ? 1 : 0;
}
int
bu_process_alive(struct bu_process *pinfo)
{
if (pinfo && pinfo->subprocess_p)
return subprocess_alive(pinfo->subprocess_p);
return 0;
}
int
bu_interactive(void)
{
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -729,8 +729,7 @@ BRLCAD_ADD_TEST(NAME bu_hook_saverestore COMMAND bu_test hook saverestore)
# bu_test runs its process tests
BRLCAD_ADDEXEC(bu_subprocess subprocess.cpp libbu TEST)
add_dependencies(bu_test bu_subprocess)
BRLCAD_ADD_TEST(NAME bu_process_basic COMMAND bu_test process "$<TARGET_FILE:bu_subprocess>" basic)
BRLCAD_ADD_TEST(NAME bu_process_abort COMMAND bu_test process "$<TARGET_FILE:bu_subprocess>" abort)
BRLCAD_ADD_TEST(NAME bu_process_all COMMAND bu_test process "$<TARGET_FILE:bu_subprocess>" all)
# *********** mappedfile.c tests ************
BRLCAD_ADD_TEST(NAME bu_mappedfile_serial_1 COMMAND bu_test mappedfile 1 1)
+491 -52
View File
@@ -32,72 +32,511 @@
#include "bu.h"
#define PROCESS_ERROR 100
#define PROCESS_FAIL 1
#define PROCESS_PASS 0
/* tests: single stdout and stderr reads - BLOCKING READS
* bu_process_read() [stdout and stderr]
* also relies on:
* bu_process_exec()
* bu_process_wait()
*/
int _test_read(const char* cmd) {
struct bu_process* p;
const char* run_av[3] = {cmd, "basic", NULL};
int count = 0;
int aborted = 0;
char line[100] = {0};
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
if (bu_process_read((char *)line, &count, p, BU_PROCESS_STDOUT, 100) <= 0) {
fprintf(stderr, "bu_process_test[\"read\"] stdin read failed\n");
return PROCESS_FAIL;
}
if (bu_process_read((char *)line, &count, p, BU_PROCESS_STDERR, 100) <= 0) {
fprintf(stderr, "bu_process_test[\"read\"] stdout read failed\n");
return PROCESS_FAIL;
}
if (bu_process_wait(&aborted, &p, 0)) {
fprintf(stderr, "bu_process_test[\"read\"] - wait failed\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: single stderr read, attempt to read from stdout with nothing written
* bu_process_read() [stderr]
* also relies on:
* bu_process_exec()
* bu_process_wait()
*/
int _test_read_stderr(const char* cmd) {
struct bu_process* p;
const char* run_av[3] = {cmd, "basic_err", NULL};
int count = 0;
int aborted = 0;
char line[100] = {0};
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
if (bu_process_read((char *)line, &count, p, BU_PROCESS_STDERR, 100) <= 0) { // good read
fprintf(stderr, "bu_process_test[\"read\"] stdout read failed\n");
return PROCESS_FAIL;
}
if (bu_process_read((char *)line, &count, p, BU_PROCESS_STDOUT, 100) != -1) { // should fail
fprintf(stderr, "bu_process_test[\"read\"] stdin read failed\n");
return PROCESS_FAIL;
}
if (bu_process_wait(&aborted, &p, 0)) {
fprintf(stderr, "bu_process_test[\"read\"] - wait failed\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: reads with lots of output; equal distribution - BLOCKING READS
* bu_process_read() [stdout and stderr]
* also relies on:
* bu_process_exec()
* bu_process_wait()
*/
int _test_read_flood(const char* cmd) {
struct bu_process* p;
const char* run_av[3] = {cmd, "flood", NULL};
int count = 0;
char line[100] = {0};
int stdout_done = 0, stderr_done = 0; // NOTE: this would be better checked with 'alive' function
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
while (!stdout_done || !stderr_done) {
if (!stdout_done && bu_process_read((char *)line, &count, p, BU_PROCESS_STDOUT, 100) <= 0)
stdout_done = 1;
if (!stderr_done && bu_process_read((char *)line, &count, p, BU_PROCESS_STDERR, 100) <= 0)
stderr_done = 1;
}
if (bu_process_wait(NULL, &p, 0)) {
fprintf(stderr, "bu_process_test[\"read\"] - wait failed\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: basic process execution and wait
* bu_process_exec()
* bu_process_wait()
*/
int _test_exec_wait(const char* cmd) {
struct bu_process* p;
const char* run_av[3] = {cmd, "basic", NULL};
// TODO: add tests to check options flags
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
if (bu_process_wait(NULL, &p, 0)) {
fprintf(stderr, "bu_process_test[\"exec_wait\"] - wait failed\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: current process id, and running subprocess id
* bu_process_id()
* bu_process_pid()
* also relies on
* bu_process_exec()
* bu_process_wait()
*/
int _test_ids(const char* cmd) {
struct bu_process* p = NULL;
const char* run_av[3] = {cmd, "basic", NULL};
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
int curr_id = bu_process_id();
int process_id = bu_process_pid(p);
if (curr_id <= 0) {
fprintf(stderr, "bu_process_test[\"ids\"] bu_process_id() should not be 0");
return PROCESS_FAIL;
}
if (process_id <= 0) {
fprintf(stderr, "bu_process_test[\"ids\"] bu_process_pid() should not be 0");
return PROCESS_FAIL;
}
if (curr_id == process_id) {
fprintf(stderr, "bu_process_test[\"ids\"] bu_process_pid() should not equal bu_process_id()");
return PROCESS_FAIL;
}
if (bu_process_wait(NULL, &p, 0)) {
fprintf(stderr, "bu_process_test[\"ids\"] - wait failed\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: open/close stdin, stdout, stderr; test pending information on stream using fileno
* bu_process_open(),
* bu_process_close(),
* bu_process_fileno(),
* bu_process_pending()
* also relies on:
* bu_process_exec(),
* bu_process_wait()
*/
int _test_streams(const char* cmd) {
struct bu_process* p = NULL;
const char* run_av[3] = {cmd, "echo", NULL};
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
FILE* f_in = bu_process_open(p, BU_PROCESS_STDIN);
// send a test line through stdin
char line[10] = "echo_test";
fputs(line, f_in);
// subprocess is using cin.get() -> need to send newline and flush so it'll move on
fputs("\n", f_in); fflush(f_in);
// subprocess should echo on stdout and stderr
char out_read[10], err_read[10];
FILE* f_out = bu_process_open(p, BU_PROCESS_STDOUT);
FILE* f_err = bu_process_open(p, BU_PROCESS_STDERR);
int fd_out = bu_process_fileno(p, BU_PROCESS_STDOUT);
int fd_err = bu_process_fileno(p, BU_PROCESS_STDERR);
// give up to 5 seconds for process_pending to get the echo
int64_t start = bu_gettime();
while (!bu_process_pending(fd_out) && !bu_process_pending(fd_out)) {
if ((bu_gettime() - start) > BU_SEC2USEC(5)) {
fprintf(stderr, "bu_process_test[\"streams\"] - process_pending check failed\n");
return PROCESS_FAIL;
}
}
if (!bu_process_pending(fd_out) || fgets(out_read, 10, f_out) == NULL) {
fprintf(stderr, "bu_process_test[\"streams\"] - expected pending data on stdout\n");
return PROCESS_FAIL;
}
if (!bu_process_pending(fd_err) || fgets(err_read, 10, f_err) == NULL) {
fprintf(stderr, "bu_process_test[\"streams\"] - expected pending data on stderr\n");
return PROCESS_FAIL;
}
// verify echo's were correct
if (bu_strncmp(out_read, err_read, 10) || bu_strncmp(out_read, line, 10)) {
fprintf(stderr, "bu_process_test[\"streams\"] - bad echo data\n");
return PROCESS_FAIL;
}
bu_process_close(p, BU_PROCESS_STDIN);
bu_process_close(p, BU_PROCESS_STDOUT);
bu_process_close(p, BU_PROCESS_STDERR);
if (bu_process_wait(NULL, &p, 0) == -1) {
fprintf(stderr, "bu_process_test[\"streams\"] - wait failed\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: forcefully terminate a running process
* bu_terminate()
* also relies on:
* bu_process_exec()
* bu_process_pid()
* bu_process_wait()
*/
int _test_abort(const char* cmd) {
struct bu_process* p = NULL;
const char* run_av[3] = {cmd, "echo", NULL}; // 'echo' test has inf wait on cin.get()
int aborted = 0;
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
if (!bu_terminate(bu_process_pid(p))) {
fprintf(stderr, "bu_process_test[\"terminate\"] - subprocess could not be terminated\n");
return PROCESS_FAIL;
}
if (!bu_process_wait(&aborted, &p, 0)) {
fprintf(stderr, "bu_process_test[\"terminate\"] - wait should have reported abort code\n");
return PROCESS_FAIL;
}
if (!aborted) {
fprintf(stderr, "bu_process_test[\"terminate\"] - bu_process didn't correctly report an aborted subprocess\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: process args storage and retrieval
* bu_process_args()
* also relies on:
* bu_process_exec()
* bu_process_wait()
*/
int _test_args(const char* cmd) {
struct bu_process* p;
const char* run_av[3] = {cmd, "basic", NULL};
int ck_argc;
const char* ck_cmd;
const char* const* ck_argv;
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
ck_argc = bu_process_args(&ck_cmd, &ck_argv, p);
if (ck_argc != 2) {
fprintf(stderr, "bu_process_test[\"args\"] - ck_argc got (%d), expected (%d)\n", ck_argc, 2);
return PROCESS_FAIL;
}
if (bu_strncmp(cmd, ck_cmd, 100)) {
fprintf(stderr, "bu_process_test[\"args\"] - ck_cmd got (%s), expected (%s)\n", ck_cmd, cmd);
return PROCESS_FAIL;
}
for (int i = 0; i < ck_argc; i++) {
if (bu_strncmp(run_av[i], ck_argv[i], 100)) {
fprintf(stderr, "bu_process_test[\"args\"] - ck_argv idx (%d) got (%s), expected (%s)\n", i, ck_cmd, run_av[i]);
return PROCESS_FAIL;
}
}
if (bu_process_wait(NULL, &p, 0)) {
fprintf(stderr, "bu_process_test[\"args\"] - wait failed\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: process reports alive
* bu_process_alive()
* also relies on:
* bu_process_exec()
* bu_process_wait()
*/
int _test_alive(const char* cmd) {
struct bu_process* p;
const char* run_av[3] = {cmd, "alive", NULL};
/* test alive status with bu_process_wait */
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
if (!bu_process_alive(p)) { // should be alive
fprintf(stderr, "bu_process_test[\"alive\"] alive check failed\n");
return PROCESS_FAIL;
}
if (bu_process_wait(NULL, &p, 0)) {
fprintf(stderr, "bu_process_test[\"alive\"] - wait failed\n");
return PROCESS_FAIL;
}
if (bu_process_alive(p)) { // should be dead
fprintf(stderr, "bu_process_test[\"alive\"] alive check failed after wait\n");
return PROCESS_FAIL;
}
/* test alive status with bu_terminate */
bu_process_exec(&p, cmd, 2, (const char**)run_av, 0, 0);
if (!bu_process_alive(p)) { // should be alive
fprintf(stderr, "bu_process_test[\"alive\"] alive1 check failed\n");
return PROCESS_FAIL;
}
if (!bu_terminate(bu_process_pid(p))) {
fprintf(stderr, "bu_process_test[\"alive\"] - terminate failed\n");
return PROCESS_FAIL;
}
int64_t start_time = bu_gettime();
while ((bu_gettime() - start_time) < BU_SEC2USEC(.1)) ; // need slight delay so bu_terminate can settle
if (bu_process_alive(p)) { // should be dead
fprintf(stderr, "bu_process_test[\"alive\"] alive check failed after terminate\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: reads with lots of output; equal stdout and stderr distribution - ASYNC READS
* bu_process_create() ['async' option]
* bu_process_read() [stdout and stderr]
* also relies on:
* bu_process_alive()
* bu_terminate()
* bu_process_pid()
* bu_process_wait()
*/
int _test_async_balanced(const char* cmd) {
struct bu_process* p;
const char* run_av[3] = {cmd, "flood", NULL};
int count = 0;
char line[100] = {0};
int timed_out = 0;
// subprocess_option_enable_async = 0x4
bu_process_exec_(&p, cmd, 2, (const char**)run_av, 0x4);
// read for up to 2 seconds
int64_t start_time = bu_gettime();
while (bu_process_alive(p)) {
(void)bu_process_read((char *)line, &count, p, BU_PROCESS_STDOUT, 100);
(void)bu_process_read((char *)line, &count, p, BU_PROCESS_STDERR, 100);
if ((bu_gettime() - start_time) > BU_SEC2USEC(2))
timed_out = 1;
}
if (timed_out) {
bu_terminate(bu_process_pid(p));
fprintf(stderr, "bu_process_test[\"async_bal\"] - reading failed\n");
return PROCESS_FAIL;
}
if (bu_process_wait(NULL, &p, 0)) {
fprintf(stderr, "bu_process_test[\"async_bal\"] - wait failed\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
/* tests: lots of outout; unequal stdout and stderr distribution - ASYNC READS
* bu_process_create() ['async' option]
* bu_process_read() [stdout and stderr]
* also relies on:
* bu_process_alive()
* bu_terminate()
* bu_process_pid()
* bu_process_wait()
*/
int _test_async_unbalanced(const char* cmd) {
struct bu_process* p;
const char* run_av[3] = {cmd, "flood_unbal", NULL}; // NOTE: writes to stdout 10x stderr
int count = 0;
char line[100] = {0};
int timed_out = 0;
// subprocess_option_enable_async = 0x4
bu_process_exec_(&p, cmd, 2, (const char**)run_av, 0x4);
// read for up to 2 seconds
int64_t start_time = bu_gettime();
while (bu_process_alive(p)) {
(void)bu_process_read((char *)line, &count, p, BU_PROCESS_STDOUT, 100);
(void)bu_process_read((char *)line, &count, p, BU_PROCESS_STDERR, 100);
if ((bu_gettime() - start_time) > BU_SEC2USEC(2))
timed_out = 1;
}
if (timed_out) {
bu_terminate(bu_process_pid(p));
fprintf(stderr, "bu_process_test[\"async_unbal\"] - reading failed\n");
return PROCESS_FAIL;
}
if (bu_process_wait(NULL, &p, 0)) {
fprintf(stderr, "bu_process_test[\"async_unbal\"] - wait failed\n");
return PROCESS_FAIL;
}
return PROCESS_PASS;
}
typedef struct {
const char* name;
int (*func)(const char*);
} ProcessTest;
ProcessTest tests[] = {
{"exec_wait", _test_exec_wait},
{"read", _test_read},
{"read_err", _test_read_stderr},
{"read_flood", _test_read_flood},
{"ids", _test_ids},
{"streams", _test_streams},
{"abort", _test_abort},
{"args", _test_args},
// TESTS BELOW THIS REQUIRE 'ALIVE' TEST AND NON-BLOCKING READING BEHAVIOR
{"alive", _test_alive},
{"async_bal", _test_async_balanced},
{"async_unbal", _test_async_unbalanced},
};
int run_test(char* av[]) {
int failed = 0;
int run = 0;
int test_all = (BU_STR_EQUAL(av[2], "all")) ? 1 : 0;
for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
if (test_all || BU_STR_EQUAL(av[2], tests[i].name)) {
// echo what we're about to run
fprintf(stdout, "Running: \"%s %s\"\n", av[1], tests[i].name);
failed += tests[i].func(av[1]);
run = 1;
}
}
if (!run) {
// invalid test was supplied, print available
fprintf(stderr, "ERROR: testname '%s' unknown\nAvailable tests:\n", av[2]);
for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++)
fprintf(stderr, " %s\n", tests[i].name);
failed = -1;
}
return failed;
}
int
main(int ac, char *av[])
{
struct bu_process *p = NULL;
// Normally this file is part of bu_test, so only set this if it looks like
// the program name is still unset.
if (bu_getprogname()[0] == '\0')
bu_setprogname(av[0]);
// check usage
if (ac != 3) {
fprintf(stderr, "Usage: %s subprocess testname\n", av[0]);
return 1;
const char* usage = "subprocess_exe testname";
fprintf(stderr, "Usage: %s %s\n", av[0], usage);
return PROCESS_ERROR;
}
fprintf(stdout, "%s\n%s\n", av[1], av[2]);
// ensure subprocess_executable exists
if (!bu_file_exists(av[1], NULL)) {
fprintf(stderr, "Program %s not found, cannot run test\n", av[1]);
return 1;
return PROCESS_ERROR;
}
if (BU_STR_EQUAL(av[2], "basic")) {
const char *pav[3];
int count = 0;
int aborted = 0;
char line[101] = {0};
pav[0] = av[1];
pav[1] = av[2];
pav[2] = NULL;
bu_process_exec(&p, av[1], 2, (const char **)pav, 0, 0);
if (bu_process_read((char *)line, &count, p, BU_PROCESS_STDOUT, 100) <= 0) {
fprintf(stderr, "bu_process_test stdin read failed\n");
return 1;
} else {
fprintf(stdout, "got: %s\n", line);
}
if (bu_process_wait(&aborted, p, 120) == -1) {
fprintf(stderr, "bu_process_test - wait failed\n");
return 1;
}
}
if (BU_STR_EQUAL(av[2], "abort")) {
const char *pav[3];
int aborted = 0;
pav[0] = av[1];
pav[1] = av[2];
pav[2] = NULL;
bu_process_exec(&p, av[1], 2, (const char **)pav, 0, 0);
fprintf(stderr, "terminating %d\n", bu_process_pid(p));
bu_terminate(bu_process_pid(p));
if (!bu_process_wait(&aborted, p, 1)) {
fprintf(stderr, "bu_process_test - subprocess didn't abort correctly\n");
return 1;
}
if (!aborted) {
fprintf(stderr, "bu_process_test - bu_process didn't correctly report an aborted subprocess\n");
return 1;
}
}
return 0;
return run_test(av);
}
+53 -8
View File
@@ -31,10 +31,13 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <time.h>
#include "bu.h"
const char* lorem_25 = "sagittis id consectetur purus ut faucibus pulvinar elementum integer enim neque volutpat ac tincidunt vitae semper quis lectus nulla at volutpat diam ut venenatis tellus";
int
main(int argc, const char *argv[])
{
@@ -46,21 +49,63 @@ main(int argc, const char *argv[])
bu_setprogname(argv[0]);
if (BU_STR_EQUAL(argv[1], "basic")) {
// https://stackoverflow.com/a/11276503
std::this_thread::sleep_for(std::chrono::milliseconds(100));
fprintf(stdout, "Hello world!\n");
// single write to stdout and stderr
fprintf(stdout, "Howdy from stdout!\n");
fprintf(stderr, "Howdy from stderr!\n");
return 0;
}
if (BU_STR_EQUAL(argv[1], "abort")) {
// https://stackoverflow.com/a/11276503
std::this_thread::sleep_for(std::chrono::milliseconds(100));
fprintf(stderr, "Not aborted!\n");
if (BU_STR_EQUAL(argv[1], "basic_err")) {
// single write to stderr
fprintf(stderr, "Howdy from stderr!\n");
return 0;
}
if (BU_STR_EQUAL(argv[1], "echo")) {
// echo incoming line on stdout and stderr
char line[25];
std::cin.get(line, 25);
return 1;
fprintf(stdout, "%s", line);
fprintf(stderr, "%s", line);
return 0;
}
if (BU_STR_EQUAL(argv[1], "alive")) {
// delay 100ms to give time for an alive check
// cross-plat sleep function: https://stackoverflow.com/a/11276503
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return 0;
}
if (BU_STR_EQUAL(argv[1], "flood")) {
// 1000 writes to stdout and stderr
const int STDOUT_WRITE_CNT = 1000;
for (int i = 0; i < STDOUT_WRITE_CNT; i++) {
fprintf(stdout, "out: %s[%d]\n", lorem_25, i);
fprintf(stderr, "err: %s[%d]\n", lorem_25, i);
}
return 0;
}
if (BU_STR_EQUAL(argv[1], "flood_unbal")) {
// 1000 writes to stdout, 100 writes to stderr
const int STDOUT_WRITE_CNT = 1000;
for (int i = 0; i < STDOUT_WRITE_CNT; i++) {
fprintf(stdout, "out: %s[%d]\n", lorem_25, i);
if ((i % 10) == 0)
fprintf(stderr, "err: %s[%d]\n", lorem_25, i);
}
return 0;
}
return 0;
}
+4 -3
View File
@@ -1616,7 +1616,7 @@ _ged_rt_output_handler2(void *clientData, int type)
/* Either EOF has been sent or there was a read error.
* there is no need to block indefinitely */
retcode = bu_process_wait(&aborted, rrtp->p, 120);
retcode = bu_process_wait(&aborted, &rrtp->p, 120);
if (aborted)
bu_log("Raytrace aborted.\n");
@@ -1712,7 +1712,7 @@ _ged_rt_output_handler(void *clientData, int mask)
/* Either EOF has been sent or there was a read error.
* there is no need to block indefinitely */
retcode = bu_process_wait(&aborted, rrtp->p, 120);
retcode = bu_process_wait(&aborted, &rrtp->p, 120);
if (aborted)
bu_log("Raytrace aborted.\n");
@@ -1814,7 +1814,8 @@ _ged_run_rt(struct ged *gedp, int cmd_len, const char **gd_rt_cmd, int argc, con
struct ged_subprocess *run_rtp;
struct bu_process *p = NULL;
bu_process_exec(&p, gd_rt_cmd[0], cmd_len, gd_rt_cmd, 0, 0);
// subprocess_option_enable_async = 0x4
bu_process_exec_(&p, gd_rt_cmd[0], cmd_len, gd_rt_cmd, 0x4);
if (bu_process_pid(p) == -1) {
bu_vls_printf(gedp->ged_result_str, "\nunable to successfully launch subprocess: ");
+1 -1
View File
@@ -424,7 +424,7 @@ print:
bu_process_close(p, BU_PROCESS_STDOUT);
bu_process_close(p, BU_PROCESS_STDERR);
retcode = bu_process_wait(NULL, p, 0);
retcode = bu_process_wait(NULL, &p, 0);
if (retcode != 0) {
_ged_wait_status(gedp->ged_result_str, retcode);
+1 -1
View File
@@ -63,7 +63,7 @@ rtcheck_handler_cleanup(struct ged_rtcheck *rtcp)
bu_process_close(rrtp->p, BU_PROCESS_STDOUT);
/* wait for the forked process */
retcode = bu_process_wait(NULL, rrtp->p, 0);
retcode = bu_process_wait(NULL, &rrtp->p, 0);
if (retcode != 0) {
_ged_wait_status(gedp->ged_result_str, retcode);
}
+1 -1
View File
@@ -85,7 +85,7 @@ rtcheck_handler_cleanup(struct ged_rtcheck *rtcp, int type)
bu_process_close(p->p, BU_PROCESS_STDOUT);
/* wait for the forked process */
int retcode = bu_process_wait(NULL, p->p, 0);
int retcode = bu_process_wait(NULL, &p->p, 0);
if (retcode != 0) {
_ged_wait_status(gedp->ged_result_str, retcode);
}
+1 -1
View File
@@ -57,7 +57,7 @@ main(int ac, char *av[]) {
bu_log("read %d: [%s]\n", count, buffer);
}
#endif
bu_process_wait(NULL, info, 0);
bu_process_wait(NULL, &info, 0);
invocations++;
/* bu_snooze(BU_SEC2USEC(1)); */
}
+1 -1
View File
@@ -355,7 +355,7 @@ subprocess_launcher(int id, void *data)
memset(line, 0, MAXPATHLEN+501);
}
if (bu_process_wait(&aborted, p, 120)) {
if (bu_process_wait(&aborted, &p, 120)) {
bu_vls_printf(sd->sd_result, "Test %ld(process %ld): rt_cache_subprocess failed\n", sd->test_num, sd->process_num);
sd->result = 1;
}
+1 -1
View File
@@ -2463,7 +2463,7 @@ mged_finish(int exitcode)
for (size_t i = 0; i < BU_PTBL_LEN(&GEDP->ged_subp); i++) {
int aborted = 0;
struct ged_subprocess *rrp = (struct ged_subprocess *)BU_PTBL_GET(&GEDP->ged_subp, i);
if (bu_process_wait(&aborted, rrp->p, 1) != -1) {
if (bu_process_wait(&aborted, &rrp->p, 1) != -1) {
bu_ptbl_ins(&rmp, (long *)rrp);
}
}