Compare commits
19
Commits
STABLE
...
subprocess
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b60322801 | ||
|
|
b29af826f0 | ||
|
|
ffce50b77c | ||
|
|
b273dc488d | ||
|
|
afb8c9193b | ||
|
|
64cfc9ffd9 | ||
|
|
4e2a90e610 | ||
|
|
fa786a7f66 | ||
|
|
5cf286398d | ||
|
|
4a8140db46 | ||
|
|
35dbcb49a1 | ||
|
|
9cbb25d936 | ||
|
|
4a6cc740fe | ||
|
|
5d1940ca86 | ||
|
|
025158d317 | ||
|
|
50c70abdb6 | ||
|
|
6f2f7947e2 | ||
|
|
e2d26994d3 | ||
|
|
c83308339b |
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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: ");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)); */
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user