Skip to content

Commit 425afa6

Browse files
Don't use posix_spawn when file redirections are involved (except /dev/null) because the error handling is too difficult
Fix exec to correctly handle the case where a pid could not be created due to posix_spawn failing Should fix fish-shell#364
1 parent 7c09a76 commit 425afa6

File tree

4 files changed

+50
-10
lines changed

4 files changed

+50
-10
lines changed

exec.cpp

+39-9
Original file line numberDiff line numberDiff line change
@@ -504,11 +504,36 @@ static void do_builtin_io( const char *out, const char *err )
504504
}
505505
}
506506

507+
/* Returns whether we can use posix spawn for a given process in a given job.
508+
Per https://github.com/fish-shell/fish-shell/issues/364 , error handling for file redirections is too difficult with posix_spawn
509+
So in that case we use fork/exec.
510+
511+
*/
512+
static bool can_use_posix_spawn_for_job(const job_t *job, const process_t *process)
513+
{
514+
bool result = true;
515+
for (size_t idx = 0; idx < job->io.size(); idx++)
516+
{
517+
const io_data_t *io = job->io.at(idx);
518+
if (io->io_mode == IO_FILE)
519+
{
520+
const char *path = io->filename_cstr;
521+
/* This IO action is a file redirection. Only allow /dev/null, which is a common case we assume won't fail. */
522+
if (strcmp(path, "/dev/null") != 0)
523+
{
524+
result = false;
525+
break;
526+
}
527+
}
528+
}
529+
return result;
530+
}
531+
507532

508533
void exec( parser_t &parser, job_t *j )
509534
{
510535
process_t *p;
511-
pid_t pid;
536+
pid_t pid = 0;
512537
int mypipe[2];
513538
sigset_t chldset;
514539

@@ -517,10 +542,10 @@ void exec( parser_t &parser, job_t *j )
517542
io_data_t *io_buffer =0;
518543

519544
/*
520-
Set to 1 if something goes wrong while exec:ing the job, in
545+
Set to true if something goes wrong while exec:ing the job, in
521546
which case the cleanup code will kick in.
522547
*/
523-
int exec_error=0;
548+
bool exec_error = false;
524549

525550
bool needs_keepalive = false;
526551
process_t keepalive;
@@ -708,7 +733,7 @@ void exec( parser_t &parser, job_t *j )
708733
{
709734
debug( 1, PIPE_ERROR );
710735
wperror (L"pipe");
711-
exec_error=1;
736+
exec_error = true;
712737
break;
713738
}
714739

@@ -897,7 +922,7 @@ void exec( parser_t &parser, job_t *j )
897922

898923
if( builtin_stdin == -1 )
899924
{
900-
exec_error=1;
925+
exec_error = true;
901926
break;
902927
}
903928
else
@@ -1232,7 +1257,7 @@ void exec( parser_t &parser, job_t *j )
12321257

12331258
#if FISH_USE_POSIX_SPAWN
12341259
/* Prefer to use posix_spawn, since it's faster on some systems like OS X */
1235-
bool use_posix_spawn = g_use_posix_spawn;
1260+
bool use_posix_spawn = g_use_posix_spawn && can_use_posix_spawn_for_job(j, p);
12361261
if (use_posix_spawn)
12371262
{
12381263
/* Create posix spawn attributes and actions */
@@ -1254,16 +1279,21 @@ void exec( parser_t &parser, job_t *j )
12541279
posix_spawn_file_actions_destroy(&actions);
12551280
posix_spawnattr_destroy(&attr);
12561281
}
1282+
1283+
/* A 0 pid means we failed to posix_spawn. Since we have no pid, we'll never get told when it's exited, so we have to mark the process as failed. */
1284+
if (pid == 0)
1285+
{
1286+
job_mark_process_as_failed(j, p);
1287+
exec_error = true;
1288+
}
12571289
}
12581290
else
12591291
#endif
12601292
{
12611293
pid = execute_fork(false);
12621294
if (pid == 0)
12631295
{
1264-
/*
1265-
This is the child process.
1266-
*/
1296+
/* This is the child process. */
12671297
p->pid = getpid();
12681298
setup_child_process( j, p );
12691299
safe_launch_process( p, actual_cmd, argv, envv );

postfork.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ void safe_report_exec_error(int err, const char *actual_cmd, char **argv, char *
566566

567567
case ENOENT:
568568
{
569+
/* ENOENT is returned by exec() when the path fails, but also returned by posix_spawn if an open file action fails. These cases appear to be impossible to distinguish. We address this by not using posix_spawn for file redirections, so all the ENOENTs we find must be errors from exec(). */
569570
char interpreter_buff[128] = {}, *interpreter;
570571
interpreter = get_interpreter(actual_cmd, interpreter_buff, sizeof interpreter_buff);
571572
if( interpreter && 0 != access( interpreter, X_OK ) )
@@ -574,7 +575,7 @@ void safe_report_exec_error(int err, const char *actual_cmd, char **argv, char *
574575
}
575576
else
576577
{
577-
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);
578+
debug_safe(0, "The file '%s' does not exist or could not be executed.", actual_cmd);
578579
}
579580
break;
580581
}

proc.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,12 @@ static void mark_process_status( const job_t *j,
388388
}
389389
}
390390

391+
void job_mark_process_as_failed( const job_t *job, process_t *p )
392+
{
393+
/* The given process failed to even lift off (e.g. posix_spawn failed) and so doesn't have a valid pid. Mark it as dead. */
394+
p->completed = 1;
395+
}
396+
391397
/**
392398
Handle status update for child \c pid. This function is called by
393399
the signal handler, so it mustn't use malloc or any such hitech

proc.h

+3
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,9 @@ void job_handle_signal( int signal, siginfo_t *info, void *con );
509509
*/
510510
int job_signal( job_t *j, int signal );
511511

512+
/* Marks a process as failed to execute (and therefore completed) */
513+
void job_mark_process_as_failed( const job_t *job, process_t *p );
514+
512515
#ifdef HAVE__PROC_SELF_STAT
513516
/**
514517
Use the procfs filesystem to look up how many jiffies of cpu time

0 commit comments

Comments
 (0)