Skip to content

Commit

Permalink
Reuse shell fallback code for exec()
Browse files Browse the repository at this point in the history
We had three places where we determined sh fallback or not. This unifies
this to a single place. It also makes Process.exec able to handle a
string directly and determine itself whether it needs a shell or not.

Fixes rubinius#2088
  • Loading branch information
dbussink committed Dec 11, 2012
1 parent c3dfcb3 commit 2ea9978
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 60 deletions.
2 changes: 1 addition & 1 deletion kernel/common/io18.rb
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ def self.popen(str, mode="r")
return nil
end
else
Process.perform_exec "/bin/sh", ["sh", "-c", str]
Process.perform_exec str, []
end
end

Expand Down
2 changes: 1 addition & 1 deletion kernel/common/io19.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1068,7 +1068,7 @@ def self.popen(str, mode=undefined, options=undefined)

Process.perform_exec cmd.first, cmd.map(&:to_s)
else
Process.perform_exec "/bin/sh", ["sh", "-c", str]
Process.perform_exec str, []
end
end

Expand Down
7 changes: 1 addition & 6 deletions kernel/common/process18.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ def self.set_status_global(status)
def self.exec(cmd, *args)
if args.empty? and cmd.kind_of? String
raise Errno::ENOENT if cmd.empty?
if /([*?{}\[\]<>()~&|$;'`"\n])/o.match(cmd)
Process.perform_exec "/bin/sh", ["sh", "-c", cmd]
else
args = cmd.split(' ')
Process.perform_exec args.first, args
end
Process.perform_exec cmd, []
else
if ary = Rubinius::Type.try_convert(cmd, Array, :to_ary)
raise ArgumentError, "wrong first argument" unless ary.size == 2
Expand Down
5 changes: 1 addition & 4 deletions kernel/common/process19.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,8 @@ def self.adjust_argv(first, *args)
if args.empty? and cmd = Rubinius::Type.try_convert(first, String, :to_str)
if cmd.empty?
raise Errno::ENOENT
elsif /([*?{}\[\]<>()~&|$;'`"\n])/o.match(cmd)
return ["/bin/sh", ["sh", "-c", cmd]]
else
args = cmd.split(' ')
return [args.first, args]
return [cmd, []]
end
elsif cmd = Rubinius::Type.try_convert(first, Array, :to_ary)
raise ArgumentError, "wrong first argument" unless cmd.size == 2
Expand Down
103 changes: 55 additions & 48 deletions vm/builtin/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,54 @@ namespace rubinius {
return obj;
}

void exec_sh_fallback(STATE, const char* c_str, size_t c_len) {
char* s = const_cast<char*>(c_str);
bool use_sh = false;

for(;*s;s++) {
if(*s != ' ' && !ISALPHA(*s) && strchr("*?{}[]<>()~&|\\$;'`\"\n\t\r\f\v",*s)) {
use_sh = true;
break;
}
}

if(use_sh) {
execl("/bin/sh", "sh", "-c", c_str, (char*)0);
} else {
size_t max_spaces = (c_len / 2) + 2;
char** args = new char*[max_spaces];

// Now put nulls for spaces into c_str and assign each bit
// to args to create the array of char*s that execv wants.

s = const_cast<char*>(c_str);
const char* s_end = c_str + c_len;
int idx = 0;

for(;;) {
// turn the next group of spaces into nulls.
while(s < s_end && *s == ' ') {
*s = 0;
s++;
}

// Hit the end, bail.
if(s == s_end) break;

// Write the address of the next chunk here.
args[idx++] = s;

// Skip to the next space
while(s < s_end && *s != ' ') s++;
}

args[idx] = 0;

// If we added anything, then exec, otherwise fall through and fail.
if(idx > 0) execvp(args[0], args);
}
}

Object* System::vm_exec(STATE, String* path, Array* args,
CallFrame* calling_environment) {

Expand Down Expand Up @@ -238,7 +286,12 @@ namespace rubinius {
old_handlers[i] = (void*)old_action.sa_handler;
}

(void)::execvp(c_path, argv);
if(argc) {
(void)::execvp(c_path, argv);
} else {
exec_sh_fallback(state, c_path, path->byte_size());
}

int erno = errno;

// Hmmm, execvp failed, we need to recover here.
Expand Down Expand Up @@ -299,53 +352,7 @@ namespace rubinius {
dup2(fds[1], STDOUT_FILENO);
close(fds[1]);

// detect and decide to use sh or not.
char* s = const_cast<char*>(c_str);
bool use_sh = false;

for(;*s;s++) {
if(*s != ' ' && !ISALPHA(*s) && strchr("*?{}[]<>()~&|\\$;'`\"\n",*s)) {
use_sh = true;
break;
}
}

if(use_sh) {
execl("/bin/sh", "sh", "-c", c_str, (char*)0);
} else {
size_t c_size = str->byte_size();
size_t max_spaces = (c_size / 2) + 2;
char** args = new char*[max_spaces];

// Now put nulls for spaces into c_str and assign each bit
// to args to create the array of char*s that execv wants.

s = const_cast<char*>(c_str);
const char* s_end = c_str + c_size;
int idx = 0;

for(;;) {
// turn the next group of spaces into nulls.
while(s < s_end && *s == ' ') {
*s = 0;
s++;
}

// Hit the end, bail.
if(s == s_end) break;

// Write the address of the next chunk here.
args[idx++] = s;

// Skip to the next space
while(s < s_end && *s != ' ') s++;
}

args[idx] = 0;

// If we added anything, then exec, otherwise fall through and fail.
if(idx > 0) execvp(args[0], args);
}
exec_sh_fallback(state, c_str, str->byte_size());

// bad news, shouldn't be here.
std::cerr << "execvp failed: " << strerror(errno) << std::endl;
Expand Down

0 comments on commit 2ea9978

Please sign in to comment.