More work towards improving relationship between multithreading and fork

This commit is contained in:
ridiculousfish 2012-02-29 11:27:14 -08:00
parent ebba30d671
commit 8ada404c5f
9 changed files with 338 additions and 131 deletions

View file

@ -635,7 +635,7 @@ int read_blocked(int fd, void *buf, size_t count)
ssize_t write_loop(int fd, const char *buff, size_t count) ssize_t write_loop(int fd, const char *buff, size_t count)
{ {
size_t out=0; ssize_t out=0;
size_t out_cum=0; size_t out_cum=0;
while( 1 ) while( 1 )
{ {
@ -644,12 +644,12 @@ ssize_t write_loop(int fd, const char *buff, size_t count)
count - out_cum ); count - out_cum );
if (out < 0) if (out < 0)
{ {
if( errno != EAGAIN && if(errno != EAGAIN && errno != EINTR)
errno != EINTR )
{ {
return -1; return -1;
} }
} else }
else
{ {
out_cum += (size_t)out; out_cum += (size_t)out;
} }
@ -686,6 +686,79 @@ void debug( int level, const wchar_t *msg, ... )
errno = errno_old; errno = errno_old;
} }
void debug_safe(int level, const char *msg, const char *param1, const char *param2, const char *param3)
{
if (! msg)
return;
/* Can't call printf, that may allocate memory Just call write() over and over. */
if (level > debug_level)
return;
int errno_old = errno;
size_t param_idx = 0;
const char *cursor = msg;
while (*cursor != '\0') {
const char *end = strchr(cursor, '%');
if (end == NULL)
end = cursor + strlen(cursor);
write(STDERR_FILENO, cursor, end - cursor);
if (end[0] == '%' && end[1] == 's') {
/* Handle a format string */
const char *format = NULL;
switch (param_idx++) {
case 0: format = param1; break;
case 1: format = param2; break;
case 2: format = param3; break;
}
if (! format)
format = "(null)";
write(STDERR_FILENO, format, strlen(format));
cursor = end + 2;
} else if (end[0] == '\0') {
/* Must be at the end of the string */
cursor = end;
} else {
/* Some other format specifier, just skip it */
cursor = end + 1;
}
}
// We always append a newline
write(STDERR_FILENO, "\n", 1);
errno = errno_old;
}
void format_int_safe(char buff[128], int val) {
if (val == 0) {
strcpy(buff, "0");
} else {
/* Generate the string in reverse */
size_t idx = 0;
bool negative = (val < 0);
if (negative)
val = -val;
while (val > 0) {
buff[idx++] = val % 10;
val /= 10;
}
if (negative)
buff[idx++] = '-';
buff[idx++] = 0;
size_t left = 0, right = idx - 1;
while (left < right) {
char tmp = buff[left];
buff[left++] = buff[right];
buff[right--] = tmp;
}
}
}
void write_screen( const wcstring &msg, wcstring &buff ) void write_screen( const wcstring &msg, wcstring &buff )
{ {
const wchar_t *start, *pos; const wchar_t *start, *pos;
@ -1979,10 +2052,9 @@ void sb_format_size( string_buffer_t *sb,
wcstring format_size(long long sz) wcstring format_size(long long sz)
{ {
wcstring result; wcstring result;
const wchar_t *sz_name[]= const wchar_t *sz_name[]= {
{
L"kB", L"MB", L"GB", L"TB", L"PB", L"EB", L"ZB", L"YB", 0 L"kB", L"MB", L"GB", L"TB", L"PB", L"EB", L"ZB", L"YB", 0
}; };
if( sz < 0 ) if( sz < 0 )
{ {
@ -2015,10 +2087,90 @@ wcstring format_size(long long sz)
} }
} }
return result; return result;
} }
/* Crappy function to extract the most significant digit of an unsigned long long value */
static char extract_most_significant_digit(unsigned long long *xp) {
unsigned long long place_value = 1;
unsigned long long x = *xp;
while (x >= 10) {
x /= 10;
place_value *= 10;
}
*xp -= (place_value * x);
return x + '0';
}
void append_ull(char *buff, unsigned long long val, size_t *inout_idx, size_t max_len) {
size_t idx = *inout_idx;
while (val > 0 && idx < max_len)
buff[idx++] = extract_most_significant_digit(&val);
*inout_idx = idx;
}
void append_str(char *buff, const char *str, size_t *inout_idx, size_t max_len) {
size_t idx = *inout_idx;
while (*str && idx < max_len)
buff[idx++] = *str++;
*inout_idx = idx;
}
void format_size_safe(char buff[128], unsigned long long sz) {
const size_t buff_size = 128;
const size_t max_len = buff_size - 1; //need to leave room for a null terminator
bzero(buff, buff_size);
size_t idx = 0;
const char * const sz_name[]= {
"kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", NULL
};
if (sz < 1)
{
strncpy(buff, "empty", buff_size);
}
else if (sz < 1024)
{
append_ull(buff, sz, &idx, max_len);
append_str(buff, "B", &idx, max_len);
}
else
{
for( size_t i=0; sz_name[i]; i++ )
{
if( sz < (1024*1024) || !sz_name[i+1] )
{
unsigned long long isz = sz/1024;
if( isz > 9 )
{
append_ull(buff, isz, &idx, max_len);
}
else
{
if (isz == 0)
{
append_str(buff, "0", &idx, max_len);
}
else
{
append_ull(buff, isz, &idx, max_len);
}
// Maybe append a single fraction digit
unsigned long long remainder = sz % 1024;
if (remainder > 0)
{
char tmp[3] = {'.', extract_most_significant_digit(&remainder), 0};
append_str(buff, tmp, &idx, max_len);
}
}
append_str(buff, sz_name[i], &idx, max_len);
break;
}
sz /= 1024;
}
}
}
double timef() double timef()
{ {
int time_res; int time_res;
@ -2045,6 +2197,19 @@ void exit_without_destructors(int code) {
_exit(code); _exit(code);
} }
/* Helper function to convert from a null_terminated_array_t<wchar_t> to a null_terminated_array_t<char_t> */
null_terminated_array_t<char> convert_wide_array_to_narrow(const null_terminated_array_t<wchar_t> &wide_arr) {
const wchar_t *const *arr = wide_arr.get();
if (! arr)
return null_terminated_array_t<char>();
std::vector<std::string> list;
for (size_t i=0; arr[i]; i++) {
list.push_back(wcs2string(arr[i]));
}
return null_terminated_array_t<char>(list);
}
void append_path_component(wcstring &path, const wcstring &component) void append_path_component(wcstring &path, const wcstring &component)
{ {
size_t len = path.size(); size_t len = path.size();
@ -2076,6 +2241,10 @@ static void child_note_forked(void) {
} }
bool is_forked_child(void) { bool is_forked_child(void) {
if (is_child_of_fork) {
printf("Uh-oh: %d\n", getpid());
while (1) sleep(10000);
}
return is_child_of_fork; return is_child_of_fork;
} }

View file

@ -338,6 +338,7 @@ class null_terminated_array_t {
public: public:
null_terminated_array_t() : array(NULL) { } null_terminated_array_t() : array(NULL) { }
null_terminated_array_t(const string_list_t &argv) : array(NULL) { this->set(argv); }
~null_terminated_array_t() { this->free(); } ~null_terminated_array_t() { this->free(); }
/** operator=. Notice the pass-by-value parameter. */ /** operator=. Notice the pass-by-value parameter. */
@ -348,7 +349,7 @@ class null_terminated_array_t {
} }
/* Copy constructor. */ /* Copy constructor. */
null_terminated_array_t(const null_terminated_array_t &him) { null_terminated_array_t(const null_terminated_array_t &him) : array(NULL) {
this->set(him.array); this->set(him.array);
} }
@ -401,9 +402,11 @@ class null_terminated_array_t {
} }
return lst; return lst;
} }
}; };
/* Helper function to convert from a null_terminated_array_t<wchar_t> to a null_terminated_array_t<char_t> */
null_terminated_array_t<char> convert_wide_array_to_narrow(const null_terminated_array_t<wchar_t> &arr);
bool is_forked_child(); bool is_forked_child();
/* Basic scoped lock class */ /* Basic scoped lock class */
@ -666,12 +669,20 @@ int create_directory( const wcstring &d );
*/ */
void bugreport(); void bugreport();
/** /** Format the specified size (in bytes, kilobytes, etc.) into the specified stringbuffer. */
Format the specified size (in bytes, kilobytes, etc.) into the specified stringbuffer.
*/
void sb_format_size( string_buffer_t *sb, long long sz ); void sb_format_size( string_buffer_t *sb, long long sz );
wcstring format_size(long long sz); wcstring format_size(long long sz);
/** Version of format_size that does not allocate memory. */
void format_size_safe(char buff[128], unsigned long long sz);
/** Our crappier versions of debug which is guaranteed to not allocate any memory, or do anything other than call write(). This is useful after a call to fork() with threads. */
void debug_safe(int level, const char *msg, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL);
/** Writes out an int safely */
void format_int_safe(char buff[128], int val);
/** /**
Return the number of seconds from the UNIX epoch, with subsecond Return the number of seconds from the UNIX epoch, with subsecond
precision. This function uses the gettimeofday function, and will precision. This function uses the gettimeofday function, and will

170
exec.cpp
View file

@ -193,55 +193,50 @@ void close_unused_internal_pipes( io_data_t *io )
} }
/** /**
Returns the interpreter for the specified script. Returns false if file Returns the interpreter for the specified script. Returns NULL if file
is not a script with a shebang. is not a script with a shebang.
*/ */
static bool get_interpreter( const wcstring &file, wcstring &interpreter ) static char *get_interpreter( const char *command, char *interpreter, size_t buff_size )
{ {
wcstring res; int fd = open( command, O_RDONLY );
FILE *fp = wfopen( file, "r" ); if( fd >= 0 )
if( fp )
{ {
while( 1 ) size_t idx = 0;
while( idx + 1 < buff_size )
{ {
wint_t ch = getwc( fp ); char ch;
if( ch == WEOF ) ssize_t amt = read(fd, &ch, sizeof ch);
if( amt <= 0 )
break; break;
if( ch == L'\n' ) if( ch == '\n' )
break; break;
res.push_back(ch); interpreter[idx++] = ch;
} }
fclose(fp); interpreter[idx++] = '\0';
close(fd);
} }
if (strncmp(interpreter, "#! /", 4) == 0) {
if (string_prefixes_string(L"#! /", res)) { return interpreter + 3;
interpreter = 3 + res.c_str(); } else if (strncmp(interpreter, "#!/", 3) == 0) {
return true; return interpreter + 2;
} else if (string_prefixes_string(L"#!/", res)) {
interpreter = 2 + res.c_str();
return true;
} else { } else {
return false; return NULL;
} }
} }
/** /**
This function is executed by the child process created by a call to This function is executed by the child process created by a call to
fork(). It should be called after \c setup_child_process. It calls fork(). It should be called after \c setup_child_process. It calls
execve to replace the fish process image with the command specified execve to replace the fish process image with the command specified
in \c p. It never returns. in \c p. It never returns.
*/ */
static void launch_process( process_t *p ) /* Called in a forked child! Do not allocate memory, etc. */
static void safe_launch_process( process_t *p, const char *actual_cmd, char **argv, char **envv )
{ {
FILE* f;
int err; int err;
// debug( 1, L"exec '%ls'", p->argv[0] ); // debug( 1, L"exec '%ls'", p->argv[0] );
char **argv = wcsv2strv(p->get_argv());
char **envv = env_export_arr( 0 );
execve ( wcs2str(p->actual_cmd), execve ( wcs2str(p->actual_cmd),
argv, argv,
envv ); envv );
@ -253,55 +248,43 @@ static void launch_process( process_t *p )
/bin/sh if encountered. This is a weird predecessor to the shebang /bin/sh if encountered. This is a weird predecessor to the shebang
that is still sometimes used since it is supported on Windows. that is still sometimes used since it is supported on Windows.
*/ */
f = wfopen(p->actual_cmd, "r"); int fd = open(actual_cmd, O_RDONLY);
if( f ) if (fd >= 0)
{ {
char begin[1] = {0}; char begin[1] = {0};
size_t read; ssize_t amt_read = read(fd, begin, 1);
close(fd);
read = fread(begin, 1, 1, f); if( (amt_read==1) && (begin[0] == ':') )
fclose( f );
if( (read==1) && (begin[0] == ':') )
{ {
// Relaunch it with /bin/sh. Don't allocate memory, so if you have more args than this, update your silly script! Maybe this should be changed to be based on ARG_MAX somehow.
char sh_command[] = "/bin/sh";
char *argv2[128];
argv2[0] = sh_command;
for (size_t i=1; i < sizeof argv2 / sizeof *argv2; i++) {
argv2[i] = argv[i-1];
if (argv2[i] == NULL)
break;
}
wcstring_list_t argv; execve(sh_command, argv2, envv);
const wchar_t *sh_command = L"/bin/sh";
argv.push_back(sh_command);
argv.push_back(p->actual_cmd);
for(size_t i=1; p->argv(i) != NULL; i++ ){
argv.push_back(p->argv(i));
}
p->set_argv(argv);
p->actual_cmd = wcsdup(sh_command);
char **res_real = wcsv2strv( p->get_argv() );
execve ( wcs2str(p->actual_cmd),
res_real,
envv );
} }
} }
errno = err; errno = err;
debug( 0, debug_safe( 0, "Failed to execute process '%s'. Reason:", actual_cmd );
_( L"Failed to execute process '%ls'. Reason:" ),
p->actual_cmd );
switch( errno ) switch( errno )
{ {
case E2BIG: case E2BIG:
{ {
size_t sz = 0; char sz1[128], sz2[128];
char **p;
wcstring sz1, sz2;
long arg_max = -1; long arg_max = -1;
size_t sz = 0;
char **p;
for(p=argv; *p; p++) for(p=argv; *p; p++)
{ {
sz += strlen(*p)+1; sz += strlen(*p)+1;
@ -312,71 +295,81 @@ static void launch_process( process_t *p )
sz += strlen(*p)+1; sz += strlen(*p)+1;
} }
sz1 = format_size(sz); format_size_safe(sz1, sz);
arg_max = sysconf( _SC_ARG_MAX ); arg_max = sysconf( _SC_ARG_MAX );
if( arg_max > 0 ) if( arg_max > 0 )
{ {
sz2 = format_size(arg_max); format_size_safe(sz2, sz);
debug_safe(0, "The total size of the argument and environment lists %s exceeds the operating system limit of %s.", sz1, sz2);
debug( 0,
L"The total size of the argument and environment lists (%ls) exceeds the operating system limit of %ls.",
sz1.c_str(),
sz2.c_str());
} }
else else
{ {
debug( 0, debug_safe( 0, "The total size of the argument and environment lists (%s) exceeds the operating system limit.", sz1);
L"The total size of the argument and environment lists (%ls) exceeds the operating system limit.",
sz1.c_str());
} }
debug( 0, debug_safe(0, "Try running the command again with fewer arguments.");
L"Try running the command again with fewer arguments.");
exit_without_destructors(STATUS_EXEC_FAIL); exit_without_destructors(STATUS_EXEC_FAIL);
break; break;
} }
case ENOEXEC: case ENOEXEC:
{ {
wperror(L"exec"); /* Hope strerror doesn't allocate... */
const char *err = strerror(errno);
debug_safe(0, "exec: %s", err);
debug(0, L"The file '%ls' is marked as an executable but could not be run by the operating system.", p->actual_cmd); debug_safe(0, "The file '%ls' is marked as an executable but could not be run by the operating system.", actual_cmd);
exit_without_destructors(STATUS_EXEC_FAIL); exit_without_destructors(STATUS_EXEC_FAIL);
} }
case ENOENT: case ENOENT:
{ {
wcstring interpreter; char interpreter_buff[128] = {}, *interpreter;
if( get_interpreter(p->actual_cmd, interpreter) && waccess( interpreter, X_OK ) ) interpreter = get_interpreter(actual_cmd, interpreter_buff, sizeof interpreter_buff);
if( interpreter && 0 != access( interpreter, X_OK ) )
{ {
debug(0, L"The file '%ls' specified the interpreter '%ls', which is not an executable command.", p->actual_cmd, interpreter.c_str() ); debug_safe(0, "The file '%s' specified the interpreter '%s', which is not an executable command.", actual_cmd, interpreter );
} }
else else
{ {
debug(0, L"The file '%ls' or a script or ELF interpreter does not exist, or a shared library needed for file or interpreter cannot be found.", p->actual_cmd); debug_safe(0, "The file '%s' or a script or ELF interpreter does not exist, or a shared library needed for file or interpreter cannot be found.", actual_cmd);
} }
exit_without_destructors(STATUS_EXEC_FAIL); exit_without_destructors(STATUS_EXEC_FAIL);
} }
case ENOMEM: case ENOMEM:
{ {
debug(0, L"Out of memory"); debug_safe(0, "Out of memory");
exit_without_destructors(STATUS_EXEC_FAIL); exit_without_destructors(STATUS_EXEC_FAIL);
} }
default: default:
{ {
wperror(L"exec"); /* Hope strerror doesn't allocate... */
const char *err = strerror(errno);
debug_safe(0, "exec: %s", err);
// debug(0, L"The file '%ls' is marked as an executable but could not be run by the operating system.", p->actual_cmd); // debug(0, L"The file '%ls' is marked as an executable but could not be run by the operating system.", p->actual_cmd);
exit_without_destructors(STATUS_EXEC_FAIL); exit_without_destructors(STATUS_EXEC_FAIL);
} }
} }
}
/**
This function is similar to launch_process, except it is not called after a fork (i.e. it is only calls exec) and therefore it can allocate memory.
*/
static void launch_process_nofork( process_t *p )
{
ASSERT_IS_MAIN_THREAD();
ASSERT_IS_NOT_FORKED_CHILD();
char **argv = wcsv2strv(p->get_argv());
char **envv = env_export_arr( 0 );
char *actual_cmd = wcs2str(p->actual_cmd);
/* Bounce to launch_process. This never returns. */
safe_launch_process(p, actual_cmd, argv, envv);
} }
@ -630,7 +623,7 @@ void exec( parser_t &parser, job_t *j )
/* /*
launch_process _never_ returns launch_process _never_ returns
*/ */
launch_process( j->first_process ); launch_process_nofork( j->first_process );
} }
else else
{ {
@ -1209,7 +1202,6 @@ void exec( parser_t &parser, job_t *j )
} }
else else
{ {
/* Free the strings in the parent */ /* Free the strings in the parent */
free(outbuff); free(outbuff);
free(errbuff); free(errbuff);
@ -1230,6 +1222,18 @@ void exec( parser_t &parser, job_t *j )
case EXTERNAL: case EXTERNAL:
{ {
/* Get argv and envv before we fork */
null_terminated_array_t<char> argv_array = convert_wide_array_to_narrow(p->get_argv_array());
null_terminated_array_t<char> envv_array;
env_export_arr(false, envv_array);
char **envv = envv_array.get();
char **argv = argv_array.get();
std::string actual_cmd_str = wcs2string(p->actual_cmd);
const char *actual_cmd = actual_cmd_str.c_str();
pid = execute_fork(true /* must drain threads */); pid = execute_fork(true /* must drain threads */);
if( pid == 0 ) if( pid == 0 )
{ {
@ -1238,10 +1242,10 @@ void exec( parser_t &parser, job_t *j )
*/ */
p->pid = getpid(); p->pid = getpid();
setup_child_process( j, p ); setup_child_process( j, p );
launch_process( p ); safe_launch_process( p, actual_cmd, argv, envv );
/* /*
launch_process _never_ returns... safe_launch_process _never_ returns...
*/ */
} }
else else

View file

@ -147,10 +147,32 @@ static void test_escape()
free( (void *)e ); free( (void *)e );
free( (void *)u ); free( (void *)u );
} }
}
static void test_format(void) {
say( L"Testing formatting functions" );
struct { unsigned long long val; const char *expected; } tests[] = {
{ 0, "empty" },
{ 1, "1B" },
{ 2, "2B" },
{ 1024, "1kB" },
{ 1870, "1.8kB" },
{ 4322911, "4.1MB" }
};
size_t i;
for (i=0; i < sizeof tests / sizeof *tests; i++) {
char buff[128];
format_size_safe(buff, tests[i].val);
assert( ! strcmp(buff, tests[i].expected));
}
for (int j=-129; j <= 129; j++) {
char buff1[128], buff2[128];
format_int_safe(buff1, j);
sprintf(buff2, "%d", j);
assert( ! strcmp(buff1, buff2));
}
} }
/** /**
@ -698,6 +720,7 @@ int main( int argc, char **argv )
reader_init(); reader_init();
env_init(); env_init();
test_format();
test_escape(); test_escape();
test_convert(); test_convert();
test_tok(); test_tok();

View file

@ -1314,7 +1314,7 @@ void parser_t::parse_job_argument_list( process_t *p,
{ {
wchar_t *end; wchar_t *end;
if( (p->type == INTERNAL_EXEC) ) if (p->type == INTERNAL_EXEC)
{ {
error( SYNTAX_ERROR, error( SYNTAX_ERROR,
tok_get_pos( tok ), tok_get_pos( tok ),
@ -2233,7 +2233,7 @@ void parser_t::skipped_exec( job_t * j )
} }
else if( wcscmp( p->argv0(), L"case" )==0) else if( wcscmp( p->argv0(), L"case" )==0)
{ {
if( (current_block->type == SWITCH ) ) if(current_block->type == SWITCH)
{ {
exec( *this, j ); exec( *this, j );
return; return;

View file

@ -32,19 +32,6 @@
#define FD_ERROR _( L"An error occurred while redirecting file descriptor %d" ) #define FD_ERROR _( L"An error occurred while redirecting file descriptor %d" )
/**
This function should be called by both the parent process and the
child right after fork() has been called. If job control is
enabled, the child is put in the jobs group, and if the child is
also in the foreground, it is also given control of the
terminal. When called in the parent process, this function may
fail, since the child might have already finished and called
exit. The parent process may safely ignore the exit status of this
call.
Returns 0 on sucess, -1 on failiure.
*/
// PCA These calls to debug are rather sketchy because they may allocate memory. Fortunately they only occur if an error occurs. // PCA These calls to debug are rather sketchy because they may allocate memory. Fortunately they only occur if an error occurs.
int set_child_group( job_t *j, process_t *p, int print_errors ) int set_child_group( job_t *j, process_t *p, int print_errors )
{ {
@ -61,6 +48,7 @@ int set_child_group( job_t *j, process_t *p, int print_errors )
{ {
if( getpgid( p->pid) != j->pgid && print_errors ) if( getpgid( p->pid) != j->pgid && print_errors )
{ {
// PCA FIXME This is sketchy to do in a forked child because it may allocate memory. This needs to call only safe functions.
debug( 1, debug( 1,
_( L"Could not send process %d, '%ls' in job %d, '%ls' from group %d to group %d" ), _( L"Could not send process %d, '%ls' in job %d, '%ls' from group %d to group %d" ),
p->pid, p->pid,
@ -284,22 +272,6 @@ static int handle_child_io( io_data_t *io )
} }
/**
Initialize a new child process. This should be called right away
after forking in the child process. If job control is enabled for
this job, the process is put in the process group of the job, all
signal handlers are reset, signals are unblocked (this function may
only be called inside the exec function, which blocks all signals),
and all IO redirections and other file descriptor actions are
performed.
\param j the job to set up the IO for
\param p the child process to set up
\return 0 on sucess, -1 on failiure. When this function returns,
signals are always unblocked. On failiure, signal handlers, io
redirections and process group of the process is undefined.
*/
int setup_child_process( job_t *j, process_t *p ) int setup_child_process( job_t *j, process_t *p )
{ {
int res=0; int res=0;

View file

@ -19,8 +19,36 @@
#include "wutil.h" #include "wutil.h"
#include "io.h" #include "io.h"
/**
This function should be called by both the parent process and the
child right after fork() has been called. If job control is
enabled, the child is put in the jobs group, and if the child is
also in the foreground, it is also given control of the
terminal. When called in the parent process, this function may
fail, since the child might have already finished and called
exit. The parent process may safely ignore the exit status of this
call.
Returns 0 on sucess, -1 on failiure.
*/
int set_child_group( job_t *j, process_t *p, int print_errors ); int set_child_group( job_t *j, process_t *p, int print_errors );
/**
Initialize a new child process. This should be called right away
after forking in the child process. If job control is enabled for
this job, the process is put in the process group of the job, all
signal handlers are reset, signals are unblocked (this function may
only be called inside the exec function, which blocks all signals),
and all IO redirections and other file descriptor actions are
performed.
\param j the job to set up the IO for
\param p the child process to set up
\return 0 on sucess, -1 on failiure. When this function returns,
signals are always unblocked. On failiure, signal handlers, io
redirections and process group of the process is undefined.
*/
int setup_child_process( job_t *j, process_t *p ); int setup_child_process( job_t *j, process_t *p );
/* Call fork(), optionally waiting until we are no longer multithreaded. If the forked child doesn't do anything that could allocate memory, take a lock, etc. (like call exec), then it's not necessary to wait for threads to die. If the forked child may do those things, it should wait for threads to die. /* Call fork(), optionally waiting until we are no longer multithreaded. If the forked child doesn't do anything that could allocate memory, take a lock, etc. (like call exec), then it's not necessary to wait for threads to die. If the forked child may do those things, it should wait for threads to die.

View file

@ -335,7 +335,6 @@ int job_signal( job_t *j, int signal )
} }
return res; return res;
} }

1
proc.h
View file

@ -172,6 +172,7 @@ class process_t
/** Returns argv */ /** Returns argv */
const wchar_t * const *get_argv(void) const { return argv_array.get(); } const wchar_t * const *get_argv(void) const { return argv_array.get(); }
const null_terminated_array_t<wchar_t> &get_argv_array(void) const { return argv_array; }
/** Returns argv[0] */ /** Returns argv[0] */
const wchar_t *argv0(void) const { return argv_array.get()[0]; } const wchar_t *argv0(void) const { return argv_array.get()[0]; }