Adopt posix_spawn (!)

Rewrite IO chains to be a vector of pointers, instead of a linked list
Removed io_transmogrify
This commit is contained in:
ridiculousfish 2012-08-15 00:57:56 -07:00
parent ad6645c48d
commit 61686aff34
27 changed files with 805 additions and 579 deletions

View file

@ -28,14 +28,14 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
D025C02A15D1FEA100B9DB63 /* completions in Resources */ = {isa = PBXBuildFile; fileRef = D025C02715D1FEA100B9DB63 /* completions */; };
D025C02B15D1FEA100B9DB63 /* functions in Resources */ = {isa = PBXBuildFile; fileRef = D025C02815D1FEA100B9DB63 /* functions */; };
D025C02C15D1FEA100B9DB63 /* tools in Resources */ = {isa = PBXBuildFile; fileRef = D025C02915D1FEA100B9DB63 /* tools */; };
D07A7D3C15A7A38100811FC6 /* builtin_scripts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0F5E28215A7A32D00315DFF /* builtin_scripts.cpp */; };
D07B247315BCC15700D4ADB4 /* add-shell in Resources */ = {isa = PBXBuildFile; fileRef = D07B247215BCC15700D4ADB4 /* add-shell */; };
D07B247615BCC4BE00D4ADB4 /* install.sh in Resources */ = {isa = PBXBuildFile; fileRef = D07B247515BCC4BE00D4ADB4 /* install.sh */; };
D0C4FD9515A7D80700212EF1 /* config.fish in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0C4FD9415A7D7EE00212EF1 /* config.fish */; };
D0C4FD9615A7D80C00212EF1 /* config.fish in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD580159EE48F0024809C /* config.fish */; };
D0CBD57B159EE4640024809C /* completions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD578159EE4600024809C /* completions */; };
D0CBD57C159EE4640024809C /* functions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD579159EE4600024809C /* functions */; };
D0CBD57D159EE4640024809C /* tools in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD57A159EE4600024809C /* tools */; };
D0CBD587159EF0E10024809C /* launch_fish.scpt in Resources */ = {isa = PBXBuildFile; fileRef = D0CBD586159EF0E10024809C /* launch_fish.scpt */; };
D0D02A67159837AD008E62BD /* complete.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853713B3ACEE0099B651 /* complete.cpp */; };
D0D02A69159837B2008E62BD /* env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853A13B3ACEE0099B651 /* env.cpp */; };
@ -121,9 +121,6 @@
D0F019F415A9772C0034B3B1 /* fish_pager in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0D02AE415986537008E62BD /* fish_pager */; };
D0F019F615A977360034B3B1 /* set_color in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0F019DC15A969970034B3B1 /* set_color */; };
D0F019F815A977AB0034B3B1 /* config.fish in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD580159EE48F0024809C /* config.fish */; };
D0F019F915A977AD0034B3B1 /* completions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD578159EE4600024809C /* completions */; };
D0F019FA15A977AE0034B3B1 /* functions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD579159EE4600024809C /* functions */; };
D0F019FB15A977B00034B3B1 /* tools in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD57A159EE4600024809C /* tools */; };
D0F019FD15A977CA0034B3B1 /* config.fish in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0C4FD9415A7D7EE00212EF1 /* config.fish */; };
D0F01A0315A978910034B3B1 /* osx_fish_launcher.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D02AFA159871B2008E62BD /* osx_fish_launcher.m */; };
D0F01A0515A978A10034B3B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0CBD583159EEE010024809C /* Foundation.framework */; };
@ -210,9 +207,6 @@
dstSubfolderSpec = 7;
files = (
D0C4FD9615A7D80C00212EF1 /* config.fish in CopyFiles */,
D0CBD57B159EE4640024809C /* completions in CopyFiles */,
D0CBD57C159EE4640024809C /* functions in CopyFiles */,
D0CBD57D159EE4640024809C /* tools in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -261,9 +255,6 @@
dstSubfolderSpec = 1;
files = (
D0F019F815A977AB0034B3B1 /* config.fish in CopyFiles */,
D0F019F915A977AD0034B3B1 /* completions in CopyFiles */,
D0F019FA15A977AE0034B3B1 /* functions in CopyFiles */,
D0F019FB15A977B00034B3B1 /* tools in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -280,6 +271,9 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
D025C02715D1FEA100B9DB63 /* completions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = completions; path = share/completions; sourceTree = "<group>"; };
D025C02815D1FEA100B9DB63 /* functions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = functions; path = share/functions; sourceTree = "<group>"; };
D025C02915D1FEA100B9DB63 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = share/tools; sourceTree = "<group>"; };
D03EE83814DF88B200FC7150 /* lru.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lru.h; sourceTree = "<group>"; };
D07B247215BCC15700D4ADB4 /* add-shell */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "add-shell"; path = "build_tools/osx_package_scripts/add-shell"; sourceTree = "<group>"; };
D07B247515BCC4BE00D4ADB4 /* install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = install.sh; path = osx/install.sh; sourceTree = "<group>"; };
@ -387,9 +381,6 @@
D0C4FD9415A7D7EE00212EF1 /* config.fish */ = {isa = PBXFileReference; lastKnownFileType = text; name = config.fish; path = etc/config.fish; sourceTree = "<group>"; };
D0C6FCC914CFA4B0004CE8AD /* autoload.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = autoload.cpp; sourceTree = "<group>"; };
D0C6FCCB14CFA4B7004CE8AD /* autoload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = autoload.h; sourceTree = "<group>"; };
D0CBD578159EE4600024809C /* completions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = completions; path = share/completions; sourceTree = "<group>"; };
D0CBD579159EE4600024809C /* functions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = functions; path = share/functions; sourceTree = "<group>"; };
D0CBD57A159EE4600024809C /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = share/tools; sourceTree = "<group>"; };
D0CBD580159EE48F0024809C /* config.fish */ = {isa = PBXFileReference; lastKnownFileType = text; name = config.fish; path = share/config.fish; sourceTree = "<group>"; };
D0CBD583159EEE010024809C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
D0CBD586159EF0E10024809C /* launch_fish.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; name = launch_fish.scpt; path = osx/launch_fish.scpt; sourceTree = "<group>"; };
@ -608,10 +599,10 @@
D0CBD580159EE48F0024809C /* config.fish */,
D0C4FD9415A7D7EE00212EF1 /* config.fish */,
D07B247515BCC4BE00D4ADB4 /* install.sh */,
D0CBD578159EE4600024809C /* completions */,
D0CBD579159EE4600024809C /* functions */,
D0CBD57A159EE4600024809C /* tools */,
D0D02AA915985C0C008E62BD /* Info.plist */,
D025C02715D1FEA100B9DB63 /* completions */,
D025C02815D1FEA100B9DB63 /* functions */,
D025C02915D1FEA100B9DB63 /* tools */,
);
name = Resources;
sourceTree = "<group>";
@ -813,6 +804,9 @@
D0CBD587159EF0E10024809C /* launch_fish.scpt in Resources */,
D07B247315BCC15700D4ADB4 /* add-shell in Resources */,
D07B247615BCC4BE00D4ADB4 /* install.sh in Resources */,
D025C02A15D1FEA100B9DB63 /* completions in Resources */,
D025C02B15D1FEA100B9DB63 /* functions in Resources */,
D025C02C15D1FEA100B9DB63 /* tools in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -155,7 +155,7 @@ static int builtin_stdin;
should normally not be used - sb_out and friends are already
configured to handle everything.
*/
static io_data_t *real_io;
static const io_chain_t *real_io;
/**
Counts the number of non null pointers in the specified array
@ -2752,7 +2752,7 @@ static int builtin_contains( parser_t &parser, wchar_t ** argv )
switch( opt )
{
case 0:
assert(opt_index >= 0 && opt_index < sizeof long_options / sizeof *long_options);
assert(opt_index >= 0 && (size_t)opt_index < sizeof long_options / sizeof *long_options);
if(long_options[opt_index].flag != 0)
break;
append_format(stderr_buffer,
@ -2878,7 +2878,7 @@ static int builtin_source( parser_t &parser, wchar_t ** argv )
parse_util_set_argv( (argc>2)?(argv+2):(argv+1), wcstring_list_t());
res = reader_read( fd, real_io );
res = reader_read( fd, real_io ? *real_io : io_chain_t() );
parser.pop_block();
@ -3470,7 +3470,7 @@ static int builtin_breakpoint( parser_t &parser, wchar_t **argv )
{
parser.push_block( BREAKPOINT );
reader_read( STDIN_FILENO, real_io );
reader_read( STDIN_FILENO, real_io ? *real_io : io_chain_t() );
parser.pop_block();
@ -3861,10 +3861,10 @@ static int internal_help( const wchar_t *cmd )
}
int builtin_run( parser_t &parser, const wchar_t * const *argv, io_data_t *io )
int builtin_run( parser_t &parser, const wchar_t * const *argv, const io_chain_t &io )
{
int (*cmd)(parser_t &parser, const wchar_t * const *argv)=0;
real_io = io;
real_io = &io;
CHECK( argv, STATUS_BUILTIN_ERROR );
CHECK( argv[0], STATUS_BUILTIN_ERROR );

View file

@ -135,7 +135,7 @@ int builtin_exists( const wcstring &cmd );
\return the exit status of the builtin command
*/
int builtin_run( parser_t &parser, const wchar_t * const *argv, io_data_t *io );
int builtin_run( parser_t &parser, const wchar_t * const *argv, const io_chain_t &io );
/** Returns a list of all builtin names */
wcstring_list_t builtin_get_names(void);

View file

@ -313,7 +313,7 @@ static void erase_values(wcstring_list_t &list, const std::vector<long> &indexes
std::set<long>::const_reverse_iterator iter;
for (iter = indexes_set.rbegin(); iter != indexes_set.rend(); iter++) {
long val = *iter;
if (val > 0 && val <= list.size()) {
if (val > 0 && (size_t)val <= list.size()) {
// One-based indexing!
list.erase(list.begin() + val - 1);
}

View file

@ -135,9 +135,9 @@ extern const wchar_t *program_name;
*/
#define FATAL_EXIT() \
{ \
ssize_t exit_read_count;char exit_read_buff; \
char exit_read_buff; \
show_stackframe(); \
exit_read_count=read( 0, &exit_read_buff, 1 ); \
read( 0, &exit_read_buff, 1 ); \
exit_without_destructors( 1 ); \
} \

View file

@ -39,6 +39,8 @@ AC_SUBST(XSEL_MAN)
AC_SUBST(XSEL_BIN)
AC_SUBST(XSEL_MAN_PATH)
#
# If needed, run autoconf to regenerate the configure file
#
@ -559,7 +561,7 @@ LIBS=$LIBS_COMMON
# Check presense of various header files
#
AC_CHECK_HEADERS([getopt.h termio.h sys/resource.h term.h ncurses/term.h ncurses.h curses.h stropts.h siginfo.h sys/select.h sys/ioctl.h sys/termios.h libintl.h execinfo.h])
AC_CHECK_HEADERS([getopt.h termio.h sys/resource.h term.h ncurses/term.h ncurses.h curses.h stropts.h siginfo.h sys/select.h sys/ioctl.h sys/termios.h libintl.h execinfo.h spawn.h])
AC_CHECK_HEADER(
[regex.h],

44
env.cpp
View file

@ -101,6 +101,7 @@ struct var_entry_t
typedef std::map<wcstring, var_entry_t*> var_table_t;
bool g_log_forks = false;
bool g_use_posix_spawn = false; //will usually be set to true
/**
@ -187,7 +188,11 @@ static null_terminated_array_t<char> export_array;
Flag for checking if we need to regenerate the exported variable
array
*/
static bool has_changed = true;
static bool has_changed_exported = true;
static void mark_changed_exported()
{
has_changed_exported = true;
}
/**
This string is used to store the value of dynamically
@ -257,7 +262,7 @@ static void start_fishd()
}
parser_t &parser = parser_t::principal_parser();
parser.eval( cmd, 0, TOP );
parser.eval( cmd, io_chain_t(), TOP );
}
/**
@ -394,7 +399,7 @@ static void universal_callback( int type,
if( str )
{
has_changed=true;
mark_changed_exported();
event_t ev = event_t::variable_event(name);
ev.arguments.reset(new wcstring_list_t());
@ -682,6 +687,10 @@ void env_init(const struct config_paths_t *paths /* or NULL */)
/* Set g_log_forks */
env_var_t log_forks = env_get_string(L"fish_log_forks");
g_log_forks = ! log_forks.missing_or_empty() && from_string<bool>(log_forks);
/* Set g_use_posix_spawn. Default to true. */
env_var_t use_posix_spawn = env_get_string(L"fish_use_posix_spawn");
g_use_posix_spawn = (use_posix_spawn.missing_or_empty() ? true : from_string<bool>(use_posix_spawn));
}
void env_destroy()
@ -702,7 +711,7 @@ void env_destroy()
var_entry_t *entry = iter->second;
if( entry->exportv )
{
has_changed = true;
mark_changed_exported();
}
delete entry;
@ -741,12 +750,12 @@ int env_set(const wcstring &key, const wchar_t *val, int var_mode)
{
ASSERT_IS_MAIN_THREAD();
env_node_t *node = NULL;
bool has_changed_old = has_changed;
bool has_changed_old = has_changed_exported;
bool has_changed_new = false;
var_entry_t *e=0;
int done=0;
int is_universal = 0;
int is_universal = 0;
if( val && contains( key, L"PWD", L"HOME" ) )
{
@ -935,7 +944,8 @@ int env_set(const wcstring &key, const wchar_t *val, int var_mode)
node->exportv=1;
}
has_changed = has_changed_old || has_changed_new;
if (has_changed_old || has_changed_new)
mark_changed_exported();
}
}
@ -982,7 +992,7 @@ static int try_remove( env_node_t *n,
if( v->exportv )
{
has_changed = true;
mark_changed_exported();
}
n->env.erase(result);
@ -1279,7 +1289,8 @@ void env_push( int new_scope )
if( new_scope )
{
has_changed |= local_scope_exports(top);
if (local_scope_exports(top))
mark_changed_exported();
}
top = node;
@ -1307,7 +1318,8 @@ void env_pop()
if( killme->new_scope )
{
has_changed |= killme->exportv || local_scope_exports( killme->next );
if (killme->exportv || local_scope_exports( killme->next ))
mark_changed_exported();
}
top = top->next;
@ -1318,7 +1330,7 @@ void env_pop()
var_entry_t *entry = iter->second;
if( entry->exportv )
{
has_changed = true;
mark_changed_exported();
}
delete entry;
}
@ -1489,7 +1501,7 @@ static void update_export_array_if_necessary(bool recalc) {
env_universal_barrier();
}
if( has_changed )
if( has_changed_exported )
{
std::map<wcstring, wcstring> vals;
size_t i;
@ -1515,22 +1527,22 @@ static void update_export_array_if_necessary(bool recalc) {
std::vector<std::string> local_export_buffer;
export_func(vals, local_export_buffer );
export_array.set(local_export_buffer);
has_changed=false;
has_changed_exported=false;
}
}
char **env_export_arr( int recalc )
char **env_export_arr( bool recalc )
{
ASSERT_IS_MAIN_THREAD();
update_export_array_if_necessary(recalc != 0);
update_export_array_if_necessary(recalc);
return export_array.get();
}
void env_export_arr(bool recalc, null_terminated_array_t<char> &output)
{
ASSERT_IS_MAIN_THREAD();
update_export_array_if_necessary(recalc != 0);
update_export_array_if_necessary(recalc);
output = export_array;
}

4
env.h
View file

@ -165,7 +165,7 @@ void env_push( int new_scope );
void env_pop();
/** Returns an array containing all exported variables in a format suitable for execv. */
char **env_export_arr( int recalc );
char **env_export_arr( bool recalc );
void env_export_arr(bool recalc, null_terminated_array_t<char> &result);
/**
@ -201,5 +201,7 @@ public:
extern bool g_log_forks;
extern int g_fork_count;
extern bool g_use_posix_spawn;
#endif

View file

@ -454,7 +454,7 @@ static void event_fire_internal( const event_t *event )
parser_t &parser = parser_t::principal_parser();
parser.push_block( EVENT );
parser.current_block->state1<const event_t *>() = event;
parser.eval( buffer, 0, TOP );
parser.eval( buffer, io_chain_t(), TOP );
parser.pop_block();
proc_pop_interactive();
proc_set_last_status( prev_status );

408
exec.cpp
View file

@ -49,6 +49,7 @@
#include "expand.h"
#include "signal.h"
#include "parse_util.h"
/**
@ -148,24 +149,25 @@ int exec_pipe( int fd[2])
/**
Check if the specified fd is used as a part of a pipeline in the
specidied set of IO redirections.
This is called after fork().
\param fd the fd to search for
\param io the set of io redirections to search in
\param io_chain the set of io redirections to search in
*/
static int use_fd_in_pipe( int fd, io_data_t *io )
{
if( !io )
return 0;
if( ( io->io_mode == IO_BUFFER ) ||
( io->io_mode == IO_PIPE ) )
{
if( io->param1.pipe_fd[0] == fd ||
io->param1.pipe_fd[1] == fd )
return 1;
}
return use_fd_in_pipe( fd, io->next );
static bool use_fd_in_pipe(int fd, const io_chain_t &io_chain )
{
for (size_t idx = 0; idx < io_chain.size(); idx++)
{
const io_data_t *io = io_chain.at(idx);
if( ( io->io_mode == IO_BUFFER ) ||
( io->io_mode == IO_PIPE ) )
{
if( io->param1.pipe_fd[0] == fd ||
io->param1.pipe_fd[1] == fd )
return true;
}
}
return false;
}
@ -177,13 +179,13 @@ static int use_fd_in_pipe( int fd, io_data_t *io )
\param io the list of io redirections for this job. Pipes mentioned
here should not be closed.
*/
void close_unused_internal_pipes( io_data_t *io )
void close_unused_internal_pipes( const io_chain_t &io )
{
/* A call to exec_close will modify open_fds, so be careful how we walk */
for (size_t i=0; i < open_fds.size(); i++) {
if (open_fds[i]) {
int fd = (int)i;
if( !use_fd_in_pipe( fd, io) )
if( !use_fd_in_pipe(fd, io))
{
debug( 4, L"Close fd %d, used in other context", fd );
exec_close( fd );
@ -193,11 +195,24 @@ void close_unused_internal_pipes( io_data_t *io )
}
}
void get_unused_internal_pipes(std::vector<int> &fds, const io_chain_t &io)
{
for (size_t i=0; i < open_fds.size(); i++) {
if (open_fds[i]) {
int fd = (int)i;
if( !use_fd_in_pipe(fd, io))
{
fds.push_back(fd);
}
}
}
}
/**
Returns the interpreter for the specified script. Returns NULL if file
is not a script with a shebang.
*/
static char *get_interpreter( const char *command, char *interpreter, size_t buff_size )
char *get_interpreter( const char *command, char *interpreter, size_t buff_size )
{
// OK to not use CLO_EXEC here because this is only called after fork
int fd = open( command, O_RDONLY );
@ -239,9 +254,8 @@ static void safe_launch_process( process_t *p, const char *actual_cmd, char **ar
// debug( 1, L"exec '%ls'", p->argv[0] );
execve ( wcs2str(p->actual_cmd.c_str()),
argv,
envv );
// Wow, this wcs2str call totally allocates memory
execve ( actual_cmd, argv, envv );
err = errno;
@ -275,88 +289,8 @@ static void safe_launch_process( process_t *p, const char *actual_cmd, char **ar
}
errno = err;
debug_safe( 0, "Failed to execute process '%s'. Reason:", actual_cmd );
switch( errno )
{
case E2BIG:
{
char sz1[128], sz2[128];
long arg_max = -1;
size_t sz = 0;
char **p;
for(p=argv; *p; p++)
{
sz += strlen(*p)+1;
}
for(p=envv; *p; p++)
{
sz += strlen(*p)+1;
}
format_size_safe(sz1, sz);
arg_max = sysconf( _SC_ARG_MAX );
if( arg_max > 0 )
{
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);
}
else
{
debug_safe( 0, "The total size of the argument and environment lists (%s) exceeds the operating system limit.", sz1);
}
debug_safe(0, "Try running the command again with fewer arguments.");
exit_without_destructors(STATUS_EXEC_FAIL);
break;
}
case ENOEXEC:
{
/* Hope strerror doesn't allocate... */
const char *err = strerror(errno);
debug_safe(0, "exec: %s", err);
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);
}
case ENOENT:
{
char interpreter_buff[128] = {}, *interpreter;
interpreter = get_interpreter(actual_cmd, interpreter_buff, sizeof interpreter_buff);
if( interpreter && 0 != access( interpreter, X_OK ) )
{
debug_safe(0, "The file '%s' specified the interpreter '%s', which is not an executable command.", actual_cmd, interpreter );
}
else
{
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);
}
case ENOMEM:
{
debug_safe(0, "Out of memory");
exit_without_destructors(STATUS_EXEC_FAIL);
}
default:
{
/* 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);
exit_without_destructors(STATUS_EXEC_FAIL);
}
}
safe_report_exec_error(errno, actual_cmd, argv, envv);
exit_without_destructors(STATUS_EXEC_FAIL);
}
/**
@ -368,7 +302,7 @@ static void launch_process_nofork( process_t *p )
ASSERT_IS_NOT_FORKED_CHILD();
char **argv = wcsv2strv(p->get_argv());
char **envv = env_export_arr( 0 );
char **envv = env_export_arr( false );
char *actual_cmd = wcs2str(p->actual_cmd.c_str());
/* Bounce to launch_process. This never returns. */
@ -380,102 +314,9 @@ static void launch_process_nofork( process_t *p )
Check if the IO redirection chains contains redirections for the
specified file descriptor
*/
static int has_fd( io_data_t *d, int fd )
static int has_fd( const io_chain_t &d, int fd )
{
return io_get( d, fd ) != 0;
}
/**
Free a transmogrified io chain. Only the chain itself and resources
used by a transmogrified IO_FILE redirection are freed, since the
original chain may still be needed.
*/
static void io_untransmogrify( io_data_t * in, io_data_t *out )
{
if( !out )
return;
assert(in != NULL);
io_untransmogrify( in->next, out->next );
switch( in->io_mode )
{
case IO_FILE:
exec_close( out->param1.old_fd );
break;
}
delete out;
}
/**
Make a copy of the specified io redirection chain, but change file
redirection into fd redirection. This makes the redirection chain
suitable for use as block-level io, since the file won't be
repeatedly reopened for every command in the block, which would
reset the cursor position.
\return the transmogrified chain on sucess, or 0 on failiure
*/
static io_data_t *io_transmogrify( io_data_t * in )
{
ASSERT_IS_MAIN_THREAD();
if( !in )
return 0;
std::auto_ptr<io_data_t> out(new io_data_t);
out->fd = in->fd;
out->io_mode = IO_FD;
out->param2.close_old = 1;
out->next=0;
switch( in->io_mode )
{
/*
These redirections don't need transmogrification. They can be passed through.
*/
case IO_FD:
case IO_CLOSE:
case IO_BUFFER:
case IO_PIPE:
{
out.reset(new io_data_t(*in));
break;
}
/*
Transmogrify file redirections
*/
case IO_FILE:
{
int fd;
if( (fd=open( in->filename_cstr, in->param2.flags, OPEN_MASK ) )==-1 )
{
debug( 1,
FILE_ERROR,
in->filename_cstr );
wperror( L"open" );
return NULL;
}
out->param1.old_fd = fd;
break;
}
}
if( in->next)
{
out->next = io_transmogrify( in->next );
if( !out->next )
{
io_untransmogrify( in, out.release() );
return NULL;
}
}
return out.release();
return io_chain_get( d, fd ) != NULL;
}
/**
@ -490,28 +331,17 @@ static io_data_t *io_transmogrify( io_data_t * in )
static void internal_exec_helper( parser_t &parser,
const wchar_t *def,
enum block_type_t block_type,
io_data_t *io )
io_chain_t &ios )
{
io_data_t *io_internal = io_transmogrify( io );
int is_block_old=is_block;
is_block=1;
/*
Did the transmogrification fail - if so, set error status and return
*/
if( io && !io_internal )
{
proc_set_last_status( STATUS_EXEC_FAIL );
return;
}
signal_unblock();
parser.eval( def, io_internal, block_type );
parser.eval( def, ios, block_type );
signal_block();
io_untransmogrify( io, io_internal );
job_reap( 0 );
is_block=is_block_old;
}
@ -552,7 +382,6 @@ void exec( parser_t &parser, job_t *j )
sigset_t chldset;
io_data_t pipe_read, pipe_write;
io_data_t *tmp;
io_data_t *io_buffer =0;
@ -577,23 +406,16 @@ void exec( parser_t &parser, job_t *j )
debug( 4, L"Exec job '%ls' with id %d", j->command_wcstr(), j->job_id );
if( parser.block_io )
if( ! parser.block_io.empty() )
{
if( j->io )
{
j->io = io_add( io_duplicate(parser.block_io), j->io );
}
else
{
j->io=io_duplicate(parser.block_io);
}
io_duplicate_append(parser.block_io, j->io);
}
io_data_t *input_redirect;
for( input_redirect = j->io; input_redirect; input_redirect = input_redirect->next )
const io_data_t *input_redirect = NULL;
for (size_t idx = 0; idx < j->io.size(); idx++)
{
input_redirect = j->io.at(idx);
if( (input_redirect->io_mode == IO_BUFFER) &&
input_redirect->is_input )
{
@ -647,11 +469,9 @@ void exec( parser_t &parser, job_t *j )
pipe_write.io_mode=IO_PIPE;
pipe_write.is_input = 0;
pipe_read.next=0;
pipe_write.next=0;
pipe_write.param1.pipe_fd[0]=pipe_write.param1.pipe_fd[1]=-1;
j->io = io_add( j->io, &pipe_write );
j->io.push_back(&pipe_write);
signal_block();
@ -737,7 +557,7 @@ void exec( parser_t &parser, job_t *j )
uniprocessor systems.
*/
if( p->type == EXTERNAL )
env_export_arr( 1 );
env_export_arr( true );
/*
@ -746,7 +566,7 @@ void exec( parser_t &parser, job_t *j )
if( p == j->first_process->next )
{
j->io = io_add( j->io, &pipe_read );
j->io.push_back(&pipe_read);
}
if( p_wants_pipe )
@ -769,8 +589,9 @@ void exec( parser_t &parser, job_t *j )
This is the last element of the pipeline.
Remove the io redirection for pipe output.
*/
j->io = io_remove( j->io, &pipe_write );
io_chain_t::iterator where = std::find(j->io.begin(), j->io.end(), &pipe_write);
if (where != j->io.end())
j->io.erase(where);
}
switch( p->type )
@ -825,8 +646,8 @@ void exec( parser_t &parser, job_t *j )
if( p->next )
{
io_buffer = io_buffer_create( 0 );
j->io = io_add( j->io, io_buffer );
io_buffer = io_buffer_create( 0 );
j->io.push_back(io_buffer);
}
internal_exec_helper( parser, def, TOP, j->io );
@ -842,8 +663,8 @@ void exec( parser_t &parser, job_t *j )
{
if( p->next )
{
io_buffer = io_buffer_create( 0 );
j->io = io_add( j->io, io_buffer );
io_buffer = io_buffer_create( 0 );
j->io.push_back(io_buffer);
}
internal_exec_helper( parser, p->argv0(), TOP, j->io );
@ -864,7 +685,7 @@ void exec( parser_t &parser, job_t *j )
*/
if( p == j->first_process )
{
io_data_t *in = io_get( j->io, 0 );
const io_data_t *in = io_chain_get( j->io, 0 );
if( in )
{
@ -1040,7 +861,7 @@ void exec( parser_t &parser, job_t *j )
break;
}
j->io = io_remove( j->io, io_buffer );
io_remove( j->io, io_buffer );
io_buffer_read( io_buffer );
@ -1159,7 +980,7 @@ void exec( parser_t &parser, job_t *j )
performance quite a bit in complex completion code.
*/
io_data_t *io = io_get( j->io, 1 );
io_data_t *io = io_chain_get( j->io, 1 );
bool buffer_stdout = io && io->io_mode == IO_BUFFER;
if( ( get_stderr_buffer().empty() ) &&
@ -1172,10 +993,10 @@ void exec( parser_t &parser, job_t *j )
skip_fork = 1;
}
if (! skip_fork && ! j->io) {
if (! skip_fork && j->io.empty()) {
/* PCA for some reason, fish forks a lot, even for basic builtins like echo just to write out their buffers. I'm certain a lot of this is unnecessary, but I am not sure exactly when. If j->io is NULL, then it means there's no pipes or anything, so we can certainly just write out our data. Beyond that, we may be able to do the same if io_get returns 0 for STDOUT_FILENO and STDERR_FILENO. */
if (g_log_forks) {
printf("fork #-: Skipping fork for internal builtin for '%ls' (io is %p, job_io is %p)\n", p->argv0(), io, j->io);
printf("fork #-: Skipping fork for internal builtin for '%ls'\n", p->argv0());
}
const wcstring &out = get_stdout_buffer(), &err = get_stderr_buffer();
char *outbuff = wcs2str(out.c_str()), *errbuff = wcs2str(err.c_str());
@ -1185,8 +1006,9 @@ void exec( parser_t &parser, job_t *j )
skip_fork = 1;
}
for( io_data_t *tmp_io = j->io; tmp_io != NULL; tmp_io=tmp_io->next )
for( io_chain_t::iterator iter = j->io.begin(); iter != j->io.end(); iter++ )
{
io_data_t *tmp_io = *iter;
if( tmp_io->io_mode == IO_FILE && strcmp(tmp_io->filename_cstr, "/dev/null") != 0)
{
skip_fork = 0;
@ -1218,8 +1040,8 @@ void exec( parser_t &parser, job_t *j )
fflush(stdout);
fflush(stderr);
if (g_log_forks) {
printf("fork #%d: Executing fork for internal builtin for '%ls' (io is %p, job_io is %p)\n", g_fork_count, p->argv0(), io, j->io);
io_print(io);
printf("fork #%d: Executing fork for internal builtin for '%ls'\n", g_fork_count, p->argv0());
io_print(io_chain_t(io));
}
pid = execute_fork(false);
if( pid == 0 )
@ -1274,33 +1096,65 @@ void exec( parser_t &parser, job_t *j )
const wchar_t *file = reader_current_filename();
const wchar_t *func = parser_t::principal_parser().is_function();
printf("fork #%d: forking for '%s' in '%ls:%ls'\n", g_fork_count, actual_cmd, file ? file : L"", func ? func : L"?");
fprintf(stderr, "IO chain for %s:\n", actual_cmd);
io_print(j->io);
}
#if FISH_USE_POSIX_SPAWN
/* Prefer to use posix_spawn, since it's faster on some systems like OS X */
bool use_posix_spawn = g_use_posix_spawn;
if (use_posix_spawn)
{
/* Create posix spawn attributes and actions */
posix_spawnattr_t attr = posix_spawnattr_t();
posix_spawn_file_actions_t actions = posix_spawn_file_actions_t();
bool made_it = fork_actions_make_spawn_properties(&attr, &actions, j, p);
if (made_it)
{
/* We successfully made the attributes and actions; actually call posix_spawn */
int spawn_ret = posix_spawn(&pid, actual_cmd, &actions, &attr, argv, envv);
if (spawn_ret != 0)
{
safe_report_exec_error(spawn_ret, actual_cmd, argv, envv);
/* Make sure our pid isn't set */
pid = 0;
}
/* Clean up our actions */
posix_spawn_file_actions_destroy(&actions);
posix_spawnattr_destroy(&attr);
}
}
else
#endif
{
pid = execute_fork(false);
if (pid == 0)
{
/*
This is the child process.
*/
p->pid = getpid();
setup_child_process( j, p );
safe_launch_process( p, actual_cmd, argv, envv );
/*
safe_launch_process _never_ returns...
*/
}
}
pid = execute_fork(false);
if( pid == 0 )
{
/*
This is the child process.
*/
p->pid = getpid();
setup_child_process( j, p );
safe_launch_process( p, actual_cmd, argv, envv );
/*
safe_launch_process _never_ returns...
*/
}
else
{
/*
This is the parent process. Store away
information on the child, and possibly fice
it control over the terminal.
*/
p->pid = pid;
set_child_group( j, p, 0 );
}
/*
This is the parent process. Store away
information on the child, and possibly fice
it control over the terminal.
*/
p->pid = pid;
set_child_group( j, p, 0 );
break;
}
@ -1348,10 +1202,12 @@ void exec( parser_t &parser, job_t *j )
debug( 3, L"Job is constructed" );
j->io = io_remove( j->io, &pipe_read );
for( tmp = parser.block_io; tmp; tmp=tmp->next )
j->io = io_remove( j->io, tmp );
io_remove( j->io, &pipe_read );
for (io_chain_t::const_iterator iter = parser.block_io.begin(); iter != parser.block_io.end(); iter++)
{
io_remove( j->io, *iter );
}
job_set_flag( j, JOB_CONSTRUCTED, 1 );
@ -1400,7 +1256,7 @@ static int exec_subshell_internal( const wcstring &cmd, wcstring_list_t *lst )
prev_status = proc_get_last_status();
parser_t &parser = parser_t::principal_parser();
if( parser.eval( cmd, io_buffer, SUBST ) )
if( parser.eval( cmd, io_chain_t(io_buffer), SUBST ) )
{
status = -1;
}

8
exec.h
View file

@ -71,6 +71,12 @@ void exec_close( int fd );
int exec_pipe( int fd[2]);
/* Close all fds in open_fds. This is called from postfork.cpp */
void close_unused_internal_pipes( io_data_t *io );
void close_unused_internal_pipes( const io_chain_t &io );
/* Gets all unused internal pipes into fds */
void get_unused_internal_pipes(std::vector<int> &fds, const io_chain_t &io);
/** Gets the interpreter for a given command */
char *get_interpreter( const char *command, char *interpreter, size_t buff_size );
#endif

View file

@ -216,8 +216,10 @@ static struct config_paths_t determine_config_directory_paths(const char *argv0)
static int read_init(const struct config_paths_t &paths)
{
parser_t &parser = parser_t::principal_parser();
parser.eval( L"builtin . " + paths.data + L"/config.fish 2>/dev/null", 0, TOP );
parser.eval( L"builtin . " + paths.sysconf + L"/config.fish 2>/dev/null", 0, TOP );
const io_chain_t empty_ios;
parser.eval( L"builtin . " + paths.data + L"/config.fish 2>/dev/null", empty_ios, TOP );
parser.eval( L"builtin . " + paths.sysconf + L"/config.fish 2>/dev/null", empty_ios, TOP );
/*
We need to get the configuration directory before we can source the user configuration file
@ -232,7 +234,7 @@ static int read_init(const struct config_paths_t &paths)
{
wcstring config_dir_escaped = escape_string( config_dir, 1 );
wcstring eval_buff = format_string(L"builtin . %ls/config.fish 2>/dev/null", config_dir_escaped.c_str());
parser.eval( eval_buff, 0, TOP );
parser.eval( eval_buff, empty_ios, TOP );
}
return 1;
@ -453,12 +455,13 @@ int main( int argc, char **argv )
if (g_log_forks)
printf("%d: g_fork_count: %d\n", __LINE__, g_fork_count);
const io_chain_t empty_ios;
if( read_init(paths) )
{
if( cmd != 0 )
{
wchar_t *cmd_wcs = str2wcs( cmd );
res = parser.eval( cmd_wcs, 0, TOP );
res = parser.eval( cmd_wcs, empty_ios, TOP );
free(cmd_wcs);
reader_exit(0, 0);
}
@ -466,7 +469,7 @@ int main( int argc, char **argv )
{
if( my_optind == argc )
{
res = reader_read( STDIN_FILENO, 0 );
res = reader_read( STDIN_FILENO, empty_ios );
}
else
{
@ -511,7 +514,7 @@ int main( int argc, char **argv )
free( rel_filename );
free( abs_filename );
res = reader_read( fd, 0 );
res = reader_read( fd, empty_ios );
if( res )
{

View file

@ -451,7 +451,7 @@ static void test_parser()
err( L"Null input when evaluating undetected" );
}
#endif
if( !parser.eval( L"ls", 0, WHILE ) )
if( !parser.eval( L"ls", io_chain_t(), WHILE ) )
{
err( L"Invalid block mode when evaluating undetected" );
}

View file

@ -411,7 +411,7 @@ static wint_t input_exec_binding( const input_mapping_t &m, const wcstring &seq
*/
int last_status = proc_get_last_status();
parser_t::principal_parser().eval( m.command.c_str(), 0, TOP );
parser_t::principal_parser().eval( m.command.c_str(), io_chain_t(), TOP );
proc_set_last_status( last_status );

235
io.cpp
View file

@ -12,6 +12,8 @@ Utilities for io redirection.
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <set>
#include <algorithm>
#ifdef HAVE_SYS_TERMIOS_H
#include <sys/termios.h>
@ -98,20 +100,20 @@ void io_buffer_read( io_data_t *d )
}
io_data_t *io_buffer_create( int is_input )
io_data_t *io_buffer_create( bool is_input )
{
std::auto_ptr<io_data_t> buffer_redirect(new io_data_t);
bool success = true;
io_data_t *buffer_redirect = new io_data_t;
buffer_redirect->out_buffer_create();
buffer_redirect->io_mode=IO_BUFFER;
buffer_redirect->next=0;
buffer_redirect->is_input = is_input;
buffer_redirect->io_mode = IO_BUFFER;
buffer_redirect->is_input = is_input ? true : false;
buffer_redirect->fd=is_input?0:1;
if( exec_pipe( buffer_redirect->param1.pipe_fd ) == -1 )
{
debug( 1, PIPE_ERROR );
wperror (L"pipe");
return NULL;
success = false;
}
else if( fcntl( buffer_redirect->param1.pipe_fd[0],
F_SETFL,
@ -119,9 +121,16 @@ io_data_t *io_buffer_create( int is_input )
{
debug( 1, PIPE_ERROR );
wperror( L"fcntl" );
return NULL;
success = false;
}
return buffer_redirect.release();
if (! success)
{
delete buffer_redirect;
buffer_redirect = NULL;
}
return buffer_redirect;
}
void io_buffer_destroy( io_data_t *io_buffer )
@ -145,98 +154,146 @@ void io_buffer_destroy( io_data_t *io_buffer )
delete io_buffer;
}
io_data_t *io_add( io_data_t *list, io_data_t *element )
void io_chain_t::remove(const io_data_t *element)
{
io_data_t *curr = list;
if( curr == 0 )
return element;
while( curr->next != 0 )
curr = curr->next;
curr->next = element;
return list;
// See if you can guess why std::find doesn't work here
for (io_chain_t::iterator iter = this->begin(); iter != this->end(); ++iter)
{
if (*iter == element)
{
this->erase(iter);
break;
}
}
}
io_data_t *io_remove( io_data_t *list, io_data_t *element )
io_chain_t io_chain_t::duplicate() const
{
io_data_t *curr, *prev=0;
for( curr=list; curr; curr = curr->next )
{
if( element == curr )
{
if( prev == 0 )
{
io_data_t *tmp = element->next;
element->next = 0;
return tmp;
}
else
{
prev->next = element->next;
element->next = 0;
return list;
}
}
prev = curr;
}
return list;
io_chain_t result;
result.reserve(this->size());
for (io_chain_t::const_iterator iter = this->begin(); iter != this->end(); iter++)
{
const io_data_t *io = *iter;
result.push_back(new io_data_t(*io));
}
return result;
}
io_data_t *io_duplicate( io_data_t *l )
void io_chain_t::duplicate_append(const io_chain_t &src)
{
io_data_t *res;
if( l == 0 )
return 0;
res = new io_data_t(*l);
res->next=io_duplicate(l->next );
return res;
this->reserve(this->size() + src.size());
for (size_t idx = 0; idx < src.size(); idx++)
{
const io_data_t *src_data = src.at(idx);
this->push_back(new io_data_t(*src_data));
}
}
io_data_t *io_get( io_data_t *io, int fd )
void io_chain_t::destroy()
{
if( io == NULL )
return 0;
io_data_t *res = io_get( io->next, fd );
if( res )
return res;
if( io->fd == fd )
return io;
return 0;
for (size_t idx = 0; idx < this->size(); idx++)
{
delete this->at(idx);
}
this->clear();
}
void io_print( io_data_t *io )
void io_remove(io_chain_t &list, const io_data_t *element)
{
list.remove(element);
}
io_chain_t io_duplicate(const io_chain_t &chain)
{
return chain.duplicate();
}
void io_print(const io_chain_t &chain)
{
if (chain.empty())
{
fprintf(stderr, "Empty chain %p\n", &chain);
return;
}
fprintf(stderr, "Chain %p (%ld items):\n", &chain, (long)chain.size());
for (size_t i=0; i < chain.size(); i++) {
const io_data_t *io = chain.at(i);
fprintf(stderr, "\t%lu: fd:%d, input:%s, ", (unsigned long)i, io->fd, io->is_input ? "yes" : "no");
switch (io->io_mode)
{
case IO_FILE:
fprintf(stderr, "file (%s)\n", io->filename_cstr);
break;
case IO_PIPE:
fprintf(stderr, "pipe {%d, %d}\n", io->param1.pipe_fd[0], io->param1.pipe_fd[1]);
break;
case IO_FD:
fprintf(stderr, "FD map %d -> %d\n", io->param1.old_fd, io->fd);
break;
case IO_BUFFER:
fprintf(stderr, "buffer %p (size %lu)\n", io->out_buffer_ptr(), io->out_buffer_size());
break;
case IO_CLOSE:
fprintf(stderr, "close %d\n", io->fd);
break;
}
}
}
void io_duplicate_append( const io_chain_t &src, io_chain_t &dst )
{
return dst.duplicate_append(src);
}
void io_chain_destroy(io_chain_t &chain)
{
chain.destroy();
}
/* Return the last IO for the given fd */
const io_data_t *io_chain_t::get_io_for_fd(int fd) const
{
size_t idx = this->size();
while (idx--)
{
const io_data_t *data = this->at(idx);
if (data->fd == fd) {
return data;
}
}
return NULL;
}
io_data_t *io_chain_t::get_io_for_fd(int fd)
{
size_t idx = this->size();
while (idx--)
{
io_data_t *data = this->at(idx);
if (data->fd == fd) {
return data;
}
}
return NULL;
}
/* The old function returned the last match, so we mimic that. */
const io_data_t *io_chain_get(const io_chain_t &src, int fd)
{
return src.get_io_for_fd(fd);
}
io_data_t *io_chain_get(io_chain_t &src, int fd)
{
return src.get_io_for_fd(fd);
}
io_chain_t::io_chain_t(io_data_t *data) : std::vector<io_data_t *>(1, data)
{
}
io_chain_t::io_chain_t() : std::vector<io_data_t *>()
{
if( !io )
{
return;
}
debug( 1, L"IO fd %d, type ", io->fd );
switch( io->io_mode )
{
case IO_PIPE:
debug( 1, L"PIPE, data %d", io->param1.pipe_fd[io->fd?1:0] );
break;
case IO_FD:
debug( 1, L"FD, copy %d", io->param1.old_fd );
break;
case IO_BUFFER:
debug( 1, L"BUFFER" );
break;
default:
debug( 1, L"OTHER" );
}
io_print( io->next );
}

68
io.h
View file

@ -73,7 +73,12 @@ public:
/** Function to get a pointer to the buffer */
char *out_buffer_ptr(void) {
assert(out_buffer.get() != NULL);
return (out_buffer->size() == 0) ? NULL : &out_buffer->at(0);
return out_buffer->empty() ? NULL : &out_buffer->at(0);
}
const char *out_buffer_ptr(void) const {
assert(out_buffer.get() != NULL);
return out_buffer->empty() ? NULL : &out_buffer->at(0);
}
/** Function to get the size of the buffer */
@ -83,11 +88,8 @@ public:
}
/** Set to true if this is an input io redirection */
int is_input;
bool is_input;
/** Pointer to the next IO redirection */
io_data_t *next;
io_data_t() :
out_buffer(),
io_mode(0),
@ -95,8 +97,7 @@ public:
param1(),
param2(),
filename_cstr(NULL),
is_input(0),
next(NULL)
is_input(0)
{
}
@ -107,10 +108,8 @@ public:
param1(rhs.param1),
param2(rhs.param2),
filename_cstr(rhs.filename_cstr ? strdup(rhs.filename_cstr) : NULL),
is_input(rhs.is_input),
next(rhs.next)
is_input(rhs.is_input)
{
}
~io_data_t() {
@ -118,27 +117,44 @@ public:
}
};
/**
Join two chains of io redirections
*/
io_data_t *io_add( io_data_t *first_chain, io_data_t *decond_chain );
class io_chain_t : public std::vector<io_data_t *> {
public:
io_chain_t();
io_chain_t(io_data_t *);
void remove(const io_data_t *element);
io_chain_t duplicate() const;
void duplicate_append(const io_chain_t &src);
void destroy();
const io_data_t *get_io_for_fd(int fd) const;
io_data_t *get_io_for_fd(int fd);
};
/**
Remove the specified io redirection from the chain
*/
io_data_t *io_remove( io_data_t *list, io_data_t *element );
void io_remove(io_chain_t &list, const io_data_t *element);
/**
Make a copy of the specified chain of redirections. Uses operator new.
*/
io_data_t *io_duplicate( io_data_t *l );
/** Make a copy of the specified chain of redirections. Uses operator new. */
io_chain_t io_duplicate(const io_chain_t &chain);
/** Return a shallow copy of the specified chain of redirections that contains only the applicable redirections. That is, if there's multiple redirections for the same fd, only the second one is included. */
io_chain_t io_unique(const io_chain_t &chain);
/** Appends a copy of the specified 'src' chain of redirections to 'dst.' Uses operator new. */
void io_duplicate_append( const io_chain_t &src, io_chain_t &dst );
/** Destroys an io_chain */
void io_chain_destroy(io_chain_t &chain);
/**
Return the last io redirection in the chain for the specified file descriptor.
*/
io_data_t *io_get( io_data_t *io, int fd );
const io_data_t *io_chain_get(const io_chain_t &src, int fd);
io_data_t *io_chain_get(io_chain_t &src, int fd);
/**
@ -155,16 +171,14 @@ void io_buffer_destroy( io_data_t *io_buffer );
used to buffer the output of a command, or non-zero to buffer the
input to a command.
*/
io_data_t *io_buffer_create( int is_input );
io_data_t *io_buffer_create( bool is_input );
/**
Close output pipe, and read from input pipe until eof.
*/
void io_buffer_read( io_data_t *d );
/**
Print debug information about the specified IO redirection chain to stderr.
*/
void io_print( io_data_t *io );
/** Print debug information about the specified IO redirection chain to stderr. */
void io_print( const io_chain_t &chain );
#endif

View file

@ -1,9 +1,6 @@
/* config.h. Generated from config.h.in by configure. */
/* config.h.in. Generated from configure.ac by autoheader. */
#ifndef FISH_CONFIG_H
#define FISH_CONFIG_H
/* Define to 1 if you have the `backtrace' function. */
#define HAVE_BACKTRACE 1
@ -76,6 +73,9 @@
/* Define to 1 if you have the <siginfo.h> header file. */
/* #undef HAVE_SIGINFO_H */
/* Define to 1 if you have the <spawn.h> header file. */
#define HAVE_SPAWN_H 1
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
@ -219,5 +219,3 @@
#define __warn_unused
#define __sentinel
#endif
#endif

View file

@ -1541,7 +1541,7 @@ void parser_t::parse_job_argument_list( process_t *p,
tok_get_desc( tok_last_type(tok)) );
}
if( ! has_target || target.size() == 0 )
if( ! has_target || target.empty() )
{
if( error_code == 0 )
error( SYNTAX_ERROR,
@ -1611,7 +1611,7 @@ void parser_t::parse_job_argument_list( process_t *p,
}
}
j->io = io_add( j->io, new_io.release() );
j->io.push_back(new_io.release());
}
break;
@ -2488,14 +2488,18 @@ void parser_t::eval_job( tokenizer *tok )
}
int parser_t::eval( const wcstring &cmdStr, io_data_t *io, enum block_type_t block_type )
int parser_t::eval( const wcstring &cmdStr, const io_chain_t &io, enum block_type_t block_type )
{
const wchar_t * const cmd = cmdStr.c_str();
size_t forbid_count;
int code;
tokenizer *previous_tokenizer=current_tokenizer;
block_t *start_current_block = current_block;
io_data_t *prev_io = block_io;
/* Record the current chain so we can put it back later */
const io_chain_t prev_io = block_io;
block_io = io;
std::vector<wcstring> prev_forbidden = forbidden_function;
if( block_type == SUBST )

View file

@ -347,7 +347,7 @@ class parser_t {
event_block_list_t global_event_blocks;
/** Current block level io redirections */
io_data_t *block_io;
io_chain_t block_io;
/**
Evaluate the expressions contained in cmd.
@ -358,7 +358,7 @@ class parser_t {
\return 0 on success, 1 otherwise
*/
int eval( const wcstring &cmd, io_data_t *io, enum block_type_t block_type );
int eval( const wcstring &cmdStr, const io_chain_t &io, enum block_type_t block_type );
/**
Evaluate line as a list of parameters, i.e. tokenize it and perform parameter expansion and cmdsubst execution on the tokens.

View file

@ -9,7 +9,6 @@
#include "iothread.h"
#include "exec.h"
/** The number of times to try to call fork() before giving up */
#define FORK_LAPS 5
@ -102,39 +101,49 @@ int set_child_group( job_t *j, process_t *p, int print_errors )
return res;
}
/** Make sure the fd used by this redirection is not used by i.e. a pipe. */
static void free_fd( io_data_t *io, int fd )
/** Make sure the fd used by each redirection is not used by a pipe. */
static void free_redirected_fds_from_pipes(io_chain_t &io_chain)
{
if( !io )
return;
if( ( io->io_mode == IO_PIPE ) || ( io->io_mode == IO_BUFFER ) )
{
int i;
for( i=0; i<2; i++ )
{
if(io->param1.pipe_fd[i] == fd )
{
while(1)
{
if( (io->param1.pipe_fd[i] = dup(fd)) == -1)
{
if( errno != EINTR )
{
debug_safe_int( 1, FD_ERROR, fd );
wperror( L"dup" );
FATAL_EXIT();
}
}
else
{
break;
}
}
}
}
size_t max = io_chain.size();
for (size_t i = 0; i < max; i++)
{
int fd_to_free = io_chain.at(i)->fd;
/* We only have to worry about fds beyond the three standard ones */
if (fd_to_free <= 2)
continue;
/* Make sure the fd is not used by a pipe */
for (size_t j = 0; j < max; j++)
{
/* We're only interested in pipes */
io_data_t *possible_conflict = io_chain.at(j);
if (possible_conflict->io_mode != IO_PIPE && possible_conflict->io_mode != IO_BUFFER)
continue;
/* If the pipe is a conflict, dup it to some other value */
for (int k=0; k<2; k++)
{
/* If it's not a conflict, we don't care */
if (possible_conflict->param1.pipe_fd[k] != fd_to_free)
continue;
/* Repeat until we have a replacement fd */
int replacement_fd = -1;
while (replacement_fd < 0)
{
replacement_fd = dup(fd_to_free);
if (replacement_fd == -1 && errno != EINTR)
{
debug_safe_int( 1, FD_ERROR, fd_to_free );
wperror( L"dup" );
FATAL_EXIT();
}
}
possible_conflict->param1.pipe_fd[k] = replacement_fd;
}
}
}
free_fd( io->next, fd );
}
@ -150,25 +159,24 @@ static void free_fd( io_data_t *io, int fd )
\return 0 on sucess, -1 on failiure
*/
static int handle_child_io( io_data_t *io )
static int handle_child_io( io_chain_t &io_chain )
{
close_unused_internal_pipes( io );
for( ; io; io=io->next )
close_unused_internal_pipes( io_chain );
free_redirected_fds_from_pipes(io_chain);
for (size_t idx = 0; idx < io_chain.size(); idx++)
{
io_data_t *io = io_chain.at(idx);
int tmp;
/* If this is not the last IO redirection for this fd, then skip it. This comes about because of the funky way in which we list IO redirections: every process in a job gets the input and output redirections, even internal ones. For example, in 'cat < foo | cat | cat > bar', the middle cat sees both < foo and > bar. It also gets pipes for its fd 0 and 1, which appear after in the list. */
if (io != io_chain.get_io_for_fd(io->fd))
continue;
if( io->io_mode == IO_FD && io->fd == io->param1.old_fd )
{
continue;
}
if( io->fd > 2 )
{
/* Make sure the fd used by this redirection is not used by e.g. a pipe. */
free_fd( io, io->fd );
}
switch( io->io_mode )
{
@ -184,7 +192,7 @@ static int handle_child_io( io_data_t *io )
case IO_FILE:
{
// Here we definitely do not want to set CLO_EXEC because our child needs access
// Here we definitely do not want to set CLO_EXEC because our child needs access
if( (tmp=open( io->filename_cstr,
io->param2.flags, OPEN_MASK ) )==-1 )
{
@ -240,6 +248,7 @@ static int handle_child_io( io_data_t *io )
case IO_BUFFER:
case IO_PIPE:
{
/* If write_pipe_idx is 0, it means we're connecting to the read end (first pipe fd). If it's 1, we're connecting to the write end (second pipe fd). */
unsigned int write_pipe_idx = (io->is_input ? 0 : 1);
/*
debug( 0,
@ -256,16 +265,11 @@ static int handle_child_io( io_data_t *io )
perror( "dup2" );
return -1;
}
if( write_pipe_idx > 0 )
{
exec_close( io->param1.pipe_fd[0]);
exec_close( io->param1.pipe_fd[1]);
}
else
{
exec_close( io->param1.pipe_fd[0] );
}
if (io->param1.pipe_fd[0] >= 0)
exec_close( io->param1.pipe_fd[0]);
if (io->param1.pipe_fd[1] >= 0)
exec_close( io->param1.pipe_fd[1]);
break;
}
@ -304,8 +308,7 @@ int setup_child_process( job_t *j, process_t *p )
/* Remove all signal blocks */
signal_unblock();
return ok ? 0 : -1;
return ok ? 0 : -1;
}
int g_fork_count = 0;
@ -362,3 +365,239 @@ pid_t execute_fork(bool wait_for_threads_to_die)
FATAL_EXIT();
return 0;
}
#if FISH_USE_POSIX_SPAWN
bool fork_actions_make_spawn_properties(posix_spawnattr_t *attr, posix_spawn_file_actions_t *actions, job_t *j, process_t *p)
{
/* Initialize the output */
if (posix_spawnattr_init(attr) != 0) {
return false;
}
if (posix_spawn_file_actions_init(actions) != 0) {
posix_spawnattr_destroy(attr);
return false;
}
bool should_set_parent_group_id = false;
int desired_parent_group_id = 0;
if (job_get_flag(j, JOB_CONTROL))
{
should_set_parent_group_id = true;
// PCA: I'm quite fuzzy on process groups,
// but I believe that the default value of 0
// means that the process becomes its own
// group leader, which is what set_child_group did
// in this case. So we want this to be 0 if j->pgid is 0.
desired_parent_group_id = j->pgid;
}
/* Set the handling for job control signals back to the default. */
bool reset_signal_handlers = true;
/* Remove all signal blocks */
bool reset_sigmask = true;
/* Set our flags */
short flags = 0;
if (reset_signal_handlers)
flags |= POSIX_SPAWN_SETSIGDEF;
if (reset_sigmask)
flags |= POSIX_SPAWN_SETSIGMASK;
if (should_set_parent_group_id)
flags |= POSIX_SPAWN_SETPGROUP;
int err = 0;
if (! err)
err = posix_spawnattr_setflags(attr, flags);
/* Everybody gets default handlers */
if (! err && reset_signal_handlers)
{
sigset_t sigdefault;
get_signals_with_handlers(&sigdefault);
err = posix_spawnattr_setsigdefault(attr, &sigdefault);
}
/* No signals blocked */
sigset_t sigmask;
sigemptyset(&sigmask);
if (! err && reset_sigmask)
err = posix_spawnattr_setsigmask(attr, &sigmask);
/* Make sure that our pipes don't use an fd that the redirection itself wants to use */
free_redirected_fds_from_pipes(j->io);
/* Close unused internal pipes */
std::vector<int> files_to_close;
get_unused_internal_pipes(files_to_close, j->io);
for (size_t i = 0; ! err && i < files_to_close.size(); i++)
{
err = posix_spawn_file_actions_addclose(actions, files_to_close.at(i));
}
for (size_t idx = 0; idx < j->io.size(); idx++)
{
const io_data_t *io = j->io.at(idx);
/* If this is not the last IO redirection for this fd, then skip it. This comes about because of the funky way in which we list IO redirections: every process in a job gets the input and output redirections, even internal ones. For example, in 'cat < foo | cat | cat > bar', the middle cat sees both < foo and > bar. It also gets pipes for its fd 0 and 1, which appear after in the list. */
if (io != j->io.get_io_for_fd(io->fd))
continue;
if( io->io_mode == IO_FD && io->fd == io->param1.old_fd )
{
continue;
}
if( io->fd > 2 )
{
/* Make sure the fd used by this redirection is not used by e.g. a pipe. */
// free_fd(io_chain, io->fd );
// PCA I don't think we need to worry about this. fd redirection is pretty uncommon anyways.
}
switch (io->io_mode)
{
case IO_CLOSE:
{
if (! err)
err = posix_spawn_file_actions_addclose(actions, io->fd);
break;
}
case IO_FILE:
{
if (! err)
err = posix_spawn_file_actions_addopen(actions, io->fd, io->filename_cstr, io->param2.flags /* mode */, OPEN_MASK);
break;
}
case IO_FD:
{
if (! err)
err = posix_spawn_file_actions_adddup2(actions, io->param1.old_fd /* from */, io->fd /* to */);
break;
}
case IO_BUFFER:
case IO_PIPE:
{
unsigned int write_pipe_idx = (io->is_input ? 0 : 1);
int from_fd = io->param1.pipe_fd[write_pipe_idx];
int to_fd = io->fd;
if (! err)
err = posix_spawn_file_actions_adddup2(actions, from_fd, to_fd);
if( write_pipe_idx > 0 )
{
if (! err)
err = posix_spawn_file_actions_addclose(actions, io->param1.pipe_fd[0]);
if (! err)
err = posix_spawn_file_actions_addclose(actions, io->param1.pipe_fd[1]);
}
else
{
if (! err)
err = posix_spawn_file_actions_addclose(actions, io->param1.pipe_fd[0]);
}
break;
}
}
}
/* Clean up on error */
if (err) {
posix_spawnattr_destroy(attr);
posix_spawn_file_actions_destroy(actions);
}
return ! err;
}
#endif //FISH_USE_POSIX_SPAWN
void safe_report_exec_error(int err, const char *actual_cmd, char **argv, char **envv)
{
debug_safe( 0, "Failed to execute process '%s'. Reason:", actual_cmd );
switch( err )
{
case E2BIG:
{
char sz1[128], sz2[128];
long arg_max = -1;
size_t sz = 0;
char **p;
for(p=argv; *p; p++)
{
sz += strlen(*p)+1;
}
for(p=envv; *p; p++)
{
sz += strlen(*p)+1;
}
format_size_safe(sz1, sz);
arg_max = sysconf( _SC_ARG_MAX );
if( arg_max > 0 )
{
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);
}
else
{
debug_safe( 0, "The total size of the argument and environment lists (%s) exceeds the operating system limit.", sz1);
}
debug_safe(0, "Try running the command again with fewer arguments.");
break;
}
case ENOEXEC:
{
/* Hope strerror doesn't allocate... */
const char *err = strerror(errno);
debug_safe(0, "exec: %s", err);
debug_safe(0, "The file '%s' is marked as an executable but could not be run by the operating system.", actual_cmd);
break;
}
case ENOENT:
{
char interpreter_buff[128] = {}, *interpreter;
interpreter = get_interpreter(actual_cmd, interpreter_buff, sizeof interpreter_buff);
if( interpreter && 0 != access( interpreter, X_OK ) )
{
debug_safe(0, "The file '%s' specified the interpreter '%s', which is not an executable command.", actual_cmd, interpreter );
}
else
{
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);
}
break;
}
case ENOMEM:
{
debug_safe(0, "Out of memory");
break;
}
default:
{
/* 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);
break;
}
}
}

View file

@ -19,6 +19,15 @@
#include "wutil.h"
#include "io.h"
#if HAVE_SPAWN_H
#include <spawn.h>
#endif
#ifndef FISH_USE_POSIX_SPAWN
#define FISH_USE_POSIX_SPAWN HAVE_SPAWN_H
#endif
/**
This function should be called by both the parent process and the
child right after fork() has been called. If job control is
@ -44,6 +53,7 @@ int set_child_group( job_t *j, process_t *p, int print_errors );
\param j the job to set up the IO for
\param p the child process to set up
\param io_chain the IO chain to use (ignores the job's iochain)
\return 0 on sucess, -1 on failiure. When this function returns,
signals are always unblocked. On failiure, signal handlers, io
@ -55,4 +65,12 @@ int setup_child_process( job_t *j, process_t *p );
*/
pid_t execute_fork(bool wait_for_threads_to_die);
/** Report an error from failing to exec or posix_spawn a command */
void safe_report_exec_error(int err, const char *actual_cmd, char **argv, char **envv);
#if FISH_USE_POSIX_SPAWN
/* Initializes and fills in a posix_spawnattr_t; on success, the caller should destroy it via posix_spawnattr_destroy */
bool fork_actions_make_spawn_properties(posix_spawnattr_t *attr, posix_spawn_file_actions_t *actions, job_t *j, process_t *p);
#endif
#endif

View file

@ -491,6 +491,51 @@ static void handle_child_status( pid_t pid, int status )
return;
}
process_t::process_t() :
argv_array(),
argv0_narrow(),
type(0),
actual_cmd(),
pid(0),
pipe_write_fd(0),
pipe_read_fd(0),
completed(0),
stopped(0),
status(0),
count_help_magic(0),
next(NULL)
#ifdef HAVE__PROC_SELF_STAT
,last_time(),
last_jiffies(0)
#endif
{
}
process_t::~process_t()
{
if (this->next != NULL)
delete this->next;
}
job_t::job_t(job_id_t jobid) :
command_str(),
command_narrow(),
first_process(NULL),
pgid(0),
tmodes(),
job_id(jobid),
io(),
flags(0)
{
}
job_t::~job_t()
{
if (first_process != NULL)
delete first_process;
io_chain_destroy(this->io);
release_job_id(job_id);
}
/* This is called from a signal handler */
void job_handle_signal ( int signal, siginfo_t *info, void *con )
@ -808,12 +853,12 @@ static int select_try( job_t *j )
{
fd_set fds;
int maxfd=-1;
io_data_t *d;
FD_ZERO(&fds);
for( d = j->io; d; d=d->next )
for (size_t idx = 0; idx < j->io.size(); idx++)
{
const io_data_t *d = j->io.at(idx);
if( d->io_mode == IO_BUFFER )
{
int fd = d->param1.pipe_fd[0];
@ -846,14 +891,14 @@ static int select_try( job_t *j )
*/
static void read_try( job_t *j )
{
io_data_t *d, *buff=0;
io_data_t *buff=NULL;
/*
Find the last buffer, which is the one we want to read from
*/
for( d = j->io; d; d=d->next )
for (size_t idx = 0; idx < j->io.size(); idx++)
{
io_data_t *d = j->io.at(idx);
if( d->io_mode == IO_BUFFER )
{
buff=d;

57
proc.h
View file

@ -140,31 +140,9 @@ class process_t
public:
process_t() :
argv_array(),
argv0_narrow(),
type(0),
actual_cmd(),
pid(0),
pipe_write_fd(0),
pipe_read_fd(0),
completed(0),
stopped(0),
status(0),
count_help_magic(0),
next(NULL)
#ifdef HAVE__PROC_SELF_STAT
,last_time(),
last_jiffies(0)
#endif
{
}
process_t();
~process_t()
{
if (this->next != NULL)
delete this->next;
}
~process_t();
/**
Type of process. Can be one of \c EXTERNAL, \c
@ -321,29 +299,8 @@ class job_t
public:
job_t(job_id_t jobid) :
command_str(),
command_narrow(),
first_process(NULL),
pgid(0),
tmodes(),
job_id(jobid),
io(NULL),
flags(0)
{
}
~job_t() {
if (first_process != NULL)
delete first_process;
io_data_t *data = this->io;
while (data) {
io_data_t *tmp = data->next;
delete data;
data = tmp;
}
release_job_id(job_id);
}
job_t(job_id_t jobid);
~job_t();
/** Returns whether the command is empty. */
bool command_is_empty() const { return command_str.empty(); }
@ -389,10 +346,8 @@ class job_t
*/
const job_id_t job_id;
/**
List of all IO redirections for this job. This linked list is allocated via new, and owned by the object, which should delete them.
*/
io_data_t *io;
/** List of all IO redirections for this job. */
io_chain_t io;
/**
Bitset containing information about the job. A combination of the JOB_* constants.

View file

@ -940,7 +940,7 @@ static void run_pager( const wcstring &prefix, int is_quoted, const std::vector<
wcstring msg;
wcstring prefix_esc;
char *foo;
io_data_t *in;
wchar_t *escaped_separator;
int has_case_sensitive=0;
@ -958,7 +958,7 @@ static void run_pager( const wcstring &prefix, int is_quoted, const std::vector<
is_quoted?L"-q":L"",
prefix_esc.c_str() );
in= io_buffer_create( 1 );
io_data_t *in = io_buffer_create(true);
in->fd = 3;
escaped_separator = escape( COMPLETE_SEP_STR, 1);
@ -1040,12 +1040,14 @@ static void run_pager( const wcstring &prefix, int is_quoted, const std::vector<
term_donate();
io_data_t *out = io_buffer_create( 0 );
out->next = in;
io_data_t *out = io_buffer_create( false );
out->fd = 4;
parser_t &parser = parser_t::principal_parser();
parser.eval( cmd, out, TOP);
io_chain_t io_chain;
io_chain.push_back(out);
io_chain.push_back(in);
parser.eval( cmd, io_chain, TOP);
term_steal();
io_buffer_read( out );
@ -2062,7 +2064,7 @@ void reader_run_command( parser_t &parser, const wchar_t *cmd )
gettimeofday(&time_before, NULL);
parser.eval( cmd, 0, TOP );
parser.eval( cmd, io_chain_t(), TOP );
job_reap( 1 );
gettimeofday(&time_after, NULL);
@ -3199,7 +3201,7 @@ int reader_search_mode()
the prompt, using syntax highlighting. This is used for reading
scripts and init files.
*/
static int read_ni( int fd, io_data_t *io )
static int read_ni( int fd, const io_chain_t &io )
{
parser_t &parser = parser_t::principal_parser();
FILE *in_stream;
@ -3296,7 +3298,7 @@ static int read_ni( int fd, io_data_t *io )
return res;
}
int reader_read( int fd, io_data_t *io )
int reader_read( int fd, const io_chain_t &io )
{
int res;

View file

@ -24,7 +24,7 @@ class history_t;
/**
Read commands from \c fd until encountering EOF
*/
int reader_read( int fd, io_data_t *io);
int reader_read( int fd, const io_chain_t &io);
/**
Tell the shell that it should exit after the currently running command finishes.

View file

@ -628,6 +628,18 @@ void signal_handle( int sig, int do_handle )
sigaction( sig, &act, 0);
}
void get_signals_with_handlers(sigset_t *set)
{
sigemptyset(set);
for( int i=0; lookup[i].desc ; i++ )
{
struct sigaction act = {};
sigaction(lookup[i].signal, NULL, &act);
if (act.sa_handler != SIG_DFL)
sigaddset(set, lookup[i].signal);
}
}
void signal_block()
{
ASSERT_IS_MAIN_THREAD();

View file

@ -6,6 +6,8 @@ The library for various signal related issues
#ifndef FISH_SIGNALH
#define FISH_SIGNALH
#include <signal.h>
/**
Get the integer signal value representing the specified signal, or
-1 of no signal was found
@ -55,4 +57,9 @@ void signal_unblock();
*/
int signal_is_blocked();
/**
Returns signals with non-default handlers
*/
void get_signals_with_handlers(sigset_t *set);
#endif