package org.jruby.util.io;
import jnr.constants.platform.Errno;
import jnr.constants.platform.Fcntl;
import jnr.constants.platform.OpenFlags;
import jnr.enxio.channels.NativeDeviceChannel;
import jnr.posix.SpawnAttribute;
import jnr.posix.SpawnFileAction;
import org.jcodings.transcode.EConvFlags;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyFile;
import org.jruby.RubyFixnum;
import org.jruby.RubyHash;
import org.jruby.RubyIO;
import org.jruby.RubyNumeric;
import org.jruby.RubyProcess;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.api.API;
import org.jruby.exceptions.RaiseException;
import org.jruby.ext.fcntl.FcntlLibrary;
import org.jruby.platform.Platform;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.ShellLauncher;
import org.jruby.util.StringSupport;
import org.jruby.util.TypeConverter;
import org.jruby.util.cli.Options;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class PopenExecutor {
public static boolean nativePopenAvailable(Ruby runtime) {
return Options.NATIVE_POPEN.load() && runtime.getPosix().isNative() && !Platform.IS_WINDOWS;
}
public static IRubyObject checkPipeCommand(ThreadContext context, IRubyObject filenameOrCommand) {
RubyString filenameStr = filenameOrCommand.convertToString();
ByteList filenameByteList = filenameStr.getByteList();
final int[] chlen = {0};
if (EncodingUtils.encAscget(
filenameByteList.getUnsafeBytes(),
filenameByteList.getBegin(),
filenameByteList.getBegin() + filenameByteList.getRealSize(),
chlen,
filenameByteList.getEncoding()) == '|') {
return filenameStr.makeShared(context.runtime, chlen[0], filenameByteList.length() - 1).infectBy(filenameOrCommand);
}
return context.nil;
}
public static RubyFixnum spawn(ThreadContext context, IRubyObject[] argv) {
Ruby runtime = context.runtime;
long pid = 0;
String[] errmsg = { null };
ExecArg eargp;
IRubyObject fail_str;
eargp = execargNew(context, argv, true);
execargFixup(context, runtime, eargp);
fail_str = eargp.use_shell ? eargp.command_name : eargp.command_name;
PopenExecutor executor = new PopenExecutor();
pid = executor.spawnProcess(context, runtime, eargp, errmsg);
if (pid == -1) {
if (errmsg[0] == null) {
throw runtime.newErrnoFromErrno(executor.errno, fail_str.toString());
}
throw runtime.newErrnoFromErrno(executor.errno, errmsg[0]);
}
return runtime.newFixnum(pid);
}
public long spawnInternal(ThreadContext context, IRubyObject[] argv, String[] errmsg) {
ExecArg eargp;
long ret;
eargp = execargNew(context, argv, true);
execargFixup(context, context.runtime, eargp);
ret = spawnProcess(context, context.runtime, eargp, errmsg);
return ret;
}
long spawnProcess(ThreadContext context, Ruby runtime, ExecArg eargp, String[] errmsg) {
long pid;
RubyString prog;
ExecArg sarg = new ExecArg();
prog = eargp.use_shell ? eargp.command_name : eargp.command_name;
if (eargp.chdir_given()) {
prog = (RubyString)prog.strDup(runtime).prepend(context, RubyString.newString(runtime, "cd '" + eargp.chdir_dir + "'; "));
eargp.chdir_dir = null;
eargp.chdir_given_clear();
eargp.attributes.add(SpawnAttribute.pgroup(0));
eargp.attributes.add(SpawnAttribute.flags((short)SpawnAttribute.SETPGROUP));
}
if (execargRunOptions(context, runtime, eargp, sarg, errmsg) < 0) {
return -1;
}
if (prog != null && !eargp.use_shell) {
String[] argv = eargp.argv_str.argv;
if (argv.length > 0) {
argv[0] = prog.toString();
}
}
if (eargp.use_shell) {
pid = procSpawnSh(runtime, prog.toString(), eargp);
}
else {
String[] argv = eargp.argv_str.argv;
pid = procSpawnCmd(runtime, argv, prog.toString(), eargp);
}
if (pid == -1) {
context.setLastExitStatus(new RubyProcess.RubyStatus(runtime, runtime.getProcStatus(), 0x7f << 8, 0));
if (errno == null || errno == Errno.__UNKNOWN_CONSTANT__) {
errno = Errno.valueOf(runtime.getPosix().errno());
}
}
execargRunOptions(context, runtime, sarg, null, errmsg);
return pid;
}
long procSpawnCmdInternal(Ruby runtime, String[] argv, String prog, ExecArg eargp) {
long status;
if (prog == null)
prog = argv[0];
prog = dlnFindExeR(runtime, prog, eargp.path_env);
if (prog == null) {
errno = Errno.ENOENT;
return -1;
}
if (prog == null || prog.length() == 0) {
errno = Errno.ENOENT;
return -1;
}
status = runtime.getPosix().posix_spawnp(
prog,
eargp.fileActions,
eargp.attributes,
Arrays.asList(argv),
eargp.envp_str == null ? Collections.EMPTY_LIST : Arrays.asList(eargp.envp_str));
if (status == -1) {
if (runtime.getPosix().errno() == Errno.ENOEXEC.intValue()) {
status = runtime.getPosix().posix_spawnp(
"/bin/sh",
eargp.fileActions,
eargp.attributes,
Arrays.asList(argv),
eargp.envp_str == null ? Collections.EMPTY_LIST : Arrays.asList(eargp.envp_str));
if (status == -1) errno = Errno.ENOEXEC;
} else {
errno = Errno.valueOf(runtime.getPosix().errno());
}
}
return status;
}
long procSpawnCmd(Ruby runtime, String[] argv, String prog, ExecArg eargp) {
long pid = -1;
if (argv.length > 0 && argv[0] != null) {
pid = procSpawnCmdInternal(runtime, argv, prog, eargp);
}
return pid;
}
long procSpawnSh(Ruby runtime, String str, ExecArg eargp) {
long status;
String shell = dlnFindExeR(runtime, "sh", eargp.path_env);
status = runtime.getPosix().posix_spawnp(
shell != null ? shell : "/bin/sh",
eargp.fileActions,
eargp.attributes,
Arrays.asList("sh", "-c", str),
eargp.envp_str == null ? Collections.EMPTY_LIST : Arrays.asList(eargp.envp_str));
if (status == -1) errno = Errno.valueOf(runtime.getPosix().errno());
return status;
}
public static IRubyObject pipeOpen(ThreadContext context, IRubyObject prog, String modestr, int fmode, IOEncodable convconfig) {
IRubyObject[] argv = {prog};
ExecArg execArg = null;
if (!isPopenFork(context.runtime, (RubyString)prog))
execArg = execargNew(context, argv, true);
return new PopenExecutor().pipeOpen(context, execArg, modestr, fmode, convconfig);
}
public static IRubyObject popen(ThreadContext context, IRubyObject[] argv, RubyClass klass, Block block) {
Ruby runtime = context.runtime;
String modestr;
IRubyObject pname, port, tmp, opt = context.nil, env = context.nil;
Object pmode = EncodingUtils.vmodeVperm(null, null);
ExecArg eargp;
int[] oflags_p = {0}, fmode_p = {0};
IOEncodable.ConvConfig convconfig = new IOEncodable.ConvConfig();
int argc = argv.length;
if (argc > 1 && !(opt = TypeConverter.checkHashType(runtime, argv[argc - 1])).isNil()) --argc;
if (argc > 1 && !(env = TypeConverter.checkHashType(runtime, argv[0])).isNil()) {
--argc;
argv = Arrays.copyOfRange(argv, 1, argc + 1);
}
switch (argc) {
case 2:
EncodingUtils.vmode(pmode, argv[1]);
case 1:
pname = argv[0];
break;
default: {
int ex = opt.isNil() ? 0 : 1;
Arity.raiseArgumentError(runtime, argc + ex, 1 + ex, 2 + ex);
return null;
}
}
tmp = TypeConverter.checkArrayType(runtime, pname);
if (!tmp.isNil()) {
tmp = ((RubyArray)tmp).aryDup();
eargp = execargNew(context, ((RubyArray)tmp).toJavaArray(), false);
((RubyArray)tmp).clear();
} else {
pname = pname.convertToString();
eargp = null;
if (!isPopenFork(runtime, (RubyString)pname)) {
IRubyObject[] pname_p = {pname};
eargp = execargNew(context, pname_p, true);
pname = pname_p[0];
}
}
if (eargp != null) {
if (!opt.isNil())
opt = execargExtractOptions(context, runtime, eargp, (RubyHash)opt);
if (!env.isNil())
execargSetenv(context, runtime, eargp, env);
}
EncodingUtils.extractModeEncoding(context, convconfig, pmode, opt, oflags_p, fmode_p);
modestr = OpenFile.ioOflagsModestr(runtime, oflags_p[0]);
port = new PopenExecutor().pipeOpen(context, eargp, modestr, fmode_p[0], convconfig);
((RubyBasicObject)port).setMetaClass(klass);
return RubyIO.ensureYieldClose(context, port, block);
}
static void execargSetenv(ThreadContext context, Ruby runtime, ExecArg eargp, IRubyObject env) {
eargp.env_modification = !env.isNil() ? checkExecEnv(context, (RubyHash)env, eargp) : null;
}
public static RubyArray checkExecEnv(ThreadContext context, RubyHash hash, ExecArg pathArg) {
Ruby runtime = context.runtime;
RubyArray env;
env = runtime.newArray();
for (Map.Entry<IRubyObject, IRubyObject> entry : (Set<Map.Entry<IRubyObject, IRubyObject>>)hash.directEntrySet()) {
IRubyObject key = entry.getKey();
IRubyObject val = entry.getValue();
String k;
k = StringSupport.checkEmbeddedNulls(runtime, key).toString();
if (k.indexOf('=') != -1)
throw runtime.newArgumentError("environment name contains a equal : " + k);
if (!val.isNil())
val = StringSupport.checkEmbeddedNulls(runtime, val);
key = key.convertToString().export(context);
if (!val.isNil()) val = val.convertToString().export(context);
if (key.convertToString().toString().equalsIgnoreCase("PATH")) {
pathArg.path_env = val;
}
env.push(runtime.newArray(key, val));
}
return env;
}
static IRubyObject (ThreadContext context, Ruby runtime, ExecArg eargp, RubyHash opthash) {
return handleOptionsCommon(context, runtime, eargp, opthash, false);
}
static void checkExecOptions(ThreadContext context, Ruby runtime, RubyHash opthash, ExecArg eargp) {
handleOptionsCommon(context, runtime, eargp, opthash, true);
}
static IRubyObject handleOptionsCommon(ThreadContext context, Ruby runtime, ExecArg eargp, RubyHash opthash, boolean raise) {
if (opthash.isEmpty())
return null;
RubyHash nonopts = null;
for (Map.Entry<IRubyObject, IRubyObject> entry : (Set<Map.Entry<IRubyObject, IRubyObject>>)opthash.directEntrySet()) {
IRubyObject key = entry.getKey();
IRubyObject val = entry.getValue();
if (execargAddopt(context, runtime, eargp, key, val) != ST_CONTINUE) {
if (raise) {
if (key instanceof RubySymbol) {
switch (key.toString()) {
case "gid" :
throw runtime.newNotImplementedError("popen does not support :gid option in JRuby");
case "uid" :
throw runtime.newNotImplementedError("popen does not support :uid option in JRuby");
default :
throw runtime.newArgumentError("wrong exec option symbol: " + key);
}
}
else {
throw runtime.newArgumentError("wrong exec option: " + key);
}
}
if (nonopts == null) nonopts = RubyHash.newHash(runtime);
nonopts.op_aset(context, key, val);
}
}
return nonopts != null ? nonopts : context.nil;
}
static boolean isPopenFork(Ruby runtime, RubyString prog) {
if (prog.size() == 1 && prog.getByteList().get(0) == '-') {
throw runtime.newNotImplementedError("fork() function is unimplemented on JRuby");
}
return false;
}
private long DO_SPAWN(Ruby runtime, ExecArg eargp, String cmd, String[] args, String[] envp) {
if (eargp.use_shell) {
return procSpawnSh(runtime, eargp, cmd, envp);
}
if (cmd == null || cmd.length() == 0) {
errno = Errno.ENOENT;
return -1;
}
long ret = runtime.getPosix().posix_spawnp(
cmd,
eargp.fileActions,
eargp.attributes,
args == null ? Collections.EMPTY_LIST : Arrays.asList(args),
envp == null ? Collections.EMPTY_LIST : Arrays.asList(envp));
if (ret == -1) {
errno = Errno.valueOf(runtime.getPosix().errno());
}
return ret;
}
private long procSpawnSh(Ruby runtime, ExecArg eargp, String str, String[] envp) {
char[] sChars;
int s = 0;
sChars = str.toCharArray();
while (s < sChars.length && (sChars[s] == ' ' || sChars[s] == '\t' || sChars[s] == '\n'))
s++;
if (s >= sChars.length) {
errno = Errno.ENOENT;
return -1;
}
if (Platform.IS_WINDOWS) {
return -1;
} else {
long ret = runtime.getPosix().posix_spawnp(
"/bin/sh",
eargp.fileActions,
eargp.attributes,
Arrays.asList("sh", "-c", str),
envp == null ? Collections.EMPTY_LIST : Arrays.asList(envp));
if (ret == -1) {
errno = Errno.valueOf(runtime.getPosix().errno());
}
return ret;
}
}
private static class PopenArg {
ExecArg eargp;
int modef;
}
private static String[] ARGVSTR2ARGV(byte[][] argv_str) {
String[] argv = new String[argv_str.length];
for (int i = 0; i < argv_str.length; i++) {
if (argv_str[i] == null) continue;
argv[i] = new String(argv_str[i]);
}
return argv;
}
private Errno errno = null;
private RubyIO pipeOpen(ThreadContext context, ExecArg eargp, String modestr, int fmode, IOEncodable convconfig) {
final Ruby runtime = context.runtime;
IRubyObject prog = eargp != null ? (eargp.use_shell ? eargp.command_name : eargp.command_name) : null;
long pid = 0;
OpenFile fptr;
RubyIO port;
OpenFile write_fptr;
IRubyObject write_port;
PosixShim posix = new PosixShim(runtime);
Errno e = null;
String[] args = null;
String[] envp = null;
ExecArg sargp = new ExecArg();
int fd;
int write_fd = -1;
String cmd = null;
if (prog != null)
cmd = StringSupport.checkEmbeddedNulls(runtime, prog).toString();
if (eargp.chdir_given()) {
cmd = "cd '" + eargp.chdir_dir + "'; " + cmd;
eargp.chdir_dir = null;
eargp.chdir_given_clear();
}
if (eargp != null && !eargp.use_shell) {
args = eargp.argv_str.argv;
}
int[] pair = {-1,-1}, writePair = {-1, -1};
switch (fmode & (OpenFile.READABLE|OpenFile.WRITABLE)) {
case OpenFile.READABLE | OpenFile.WRITABLE:
if (API.rb_pipe(runtime, writePair) == -1)
throw runtime.newErrnoFromErrno(posix.getErrno(), prog.toString());
if (API.rb_pipe(runtime, pair) == -1) {
e = posix.getErrno();
runtime.getPosix().close(writePair[1]);
runtime.getPosix().close(writePair[0]);
posix.setErrno(e);
throw runtime.newErrnoFromErrno(posix.getErrno(), prog.toString());
}
if (eargp != null) prepareStdioRedirects(runtime, pair, writePair, eargp);
break;
case OpenFile.READABLE:
if (API.rb_pipe(runtime, pair) == -1)
throw runtime.newErrnoFromErrno(posix.getErrno(), prog.toString());
if (eargp != null) prepareStdioRedirects(runtime, pair, null, eargp);
break;
case OpenFile.WRITABLE:
if (API.rb_pipe(runtime, pair) == -1)
throw runtime.newErrnoFromErrno(posix.getErrno(), prog.toString());
if (eargp != null) prepareStdioRedirects(runtime, null, pair, eargp);
break;
default:
throw runtime.newSystemCallError(prog.toString());
}
if (eargp != null) {
try {
execargFixup(context, runtime, eargp);
} catch (RaiseException re) {
if (writePair[0] != -1) runtime.getPosix().close(writePair[0]);
if (writePair[1] != -1) runtime.getPosix().close(writePair[1]);
if (pair[0] != -1) runtime.getPosix().close(pair[0]);
if (pair[1] != -1) runtime.getPosix().close(pair[1]);
execargParentEnd(runtime, eargp);
throw re;
}
execargRunOptions(context, runtime, eargp, sargp, null);
if (eargp.envp_str != null) envp = eargp.envp_str;
while ((pid = DO_SPAWN(runtime, eargp, cmd, args, envp)) == -1) {
switch (e = errno) {
case EAGAIN:
case EWOULDBLOCK:
try {Thread.sleep(1000);} catch (InterruptedException ie) {}
continue;
}
break;
}
if (eargp != null)
execargRunOptions(context, runtime, sargp, null, null);
execargParentEnd(runtime, eargp);
}
else {
throw runtime.newNotImplementedError("spawn without exec args (probably a bug)");
}
if (pid == -1) {
runtime.getPosix().close(pair[1]);
runtime.getPosix().close(pair[0]);
if ((fmode & (OpenFile.READABLE|OpenFile.WRITABLE)) == (OpenFile.READABLE|OpenFile.WRITABLE)) {
runtime.getPosix().close(pair[1]);
runtime.getPosix().close(pair[0]);
}
errno = e;
throw runtime.newErrnoFromErrno(errno, prog.toString());
}
if ((fmode & OpenFile.READABLE) != 0 && (fmode & OpenFile.WRITABLE) != 0) {
runtime.getPosix().close(pair[1]);
fd = pair[0];
runtime.getPosix().close(writePair[0]);
write_fd = writePair[1];
}
else if ((fmode & OpenFile.READABLE) != 0) {
runtime.getPosix().close(pair[1]);
fd = pair[0];
}
else {
runtime.getPosix().close(pair[0]);
fd = pair[1];
}
port = (RubyIO) runtime.getIO().allocate();
fptr = port.MakeOpenFile();
fptr.setChannel(new NativeDeviceChannel(fd));
fptr.setMode(fmode | (OpenFile.SYNC|OpenFile.DUPLEX));
if (convconfig != null) {
fptr.encs.copy(convconfig);
if (Platform.IS_WINDOWS) {
if ((fptr.encs.ecflags & EncodingUtils.ECONV_DEFAULT_NEWLINE_DECORATOR) != 0) {
fptr.encs.ecflags |= EConvFlags.UNIVERSAL_NEWLINE_DECORATOR;
}
}
}
else {
if (fptr.NEED_NEWLINE_DECORATOR_ON_READ()) {
fptr.encs.ecflags |= EConvFlags.UNIVERSAL_NEWLINE_DECORATOR;
}
if (EncodingUtils.TEXTMODE_NEWLINE_DECORATOR_ON_WRITE != 0) {
if (fptr.NEED_NEWLINE_DECORATOR_ON_WRITE()) {
fptr.encs.ecflags |= EncodingUtils.TEXTMODE_NEWLINE_DECORATOR_ON_WRITE;
}
}
}
final long finalPid = pid;
fptr.setPid(pid);
fptr.setProcess(new POSIXProcess(runtime, finalPid));
if (write_fd != -1) {
write_port = runtime.getIO().allocate();
write_fptr = ((RubyIO)write_port).MakeOpenFile();
write_fptr.setChannel(new NativeDeviceChannel(write_fd));
write_fptr.setMode((fmode & ~OpenFile.READABLE)| OpenFile.SYNC|OpenFile.DUPLEX);
fptr.setMode(fptr.getMode() & ~OpenFile.WRITABLE);
fptr.tiedIOForWriting = (RubyIO)write_port;
port.setInstanceVariable("@tied_io_for_writing", write_port);
}
return port;
}
private void prepareStdioRedirects(Ruby runtime, int[] readPipe, int[] writePipe, ExecArg eargp) {
if (readPipe != null) {
int readPipeWriteFD = readPipe[1];
eargp.fd_dup2 = checkExecRedirect1(runtime, eargp.fd_dup2, runtime.newFixnum(1), runtime.newFixnum(readPipeWriteFD));
int readPipeReadFD = readPipe[0];
eargp.fileActions.add(SpawnFileAction.close(readPipeReadFD));
}
if (writePipe != null) {
int writePipeReadFD = writePipe[0];
eargp.fd_dup2 = checkExecRedirect1(runtime, eargp.fd_dup2, runtime.newFixnum(0), runtime.newFixnum(writePipeReadFD));
int writePipeWriteFD = writePipe[1];
eargp.fileActions.add(SpawnFileAction.close(writePipeWriteFD));
}
}
static int run_exec_pgroup(Ruby runtime, ExecArg eargp, ExecArg sargp, String[] errmsg) {
int ret = 0;
long pgroup;
pgroup = eargp.pgroup_pgid;
if (pgroup == -1) {
return ret;
}
eargp.attributes.add(SpawnAttribute.pgroup(pgroup));
eargp.attributes.add(SpawnAttribute.flags((short)SpawnAttribute.SETPGROUP));
return ret;
}
static int run_exec_rlimit(Ruby runtime, RubyArray ary, ExecArg sargp, String[] errmsg) {
throw runtime.newNotImplementedError("changing rlimit in child is not supported");
}
static void saveEnv(ThreadContext context, Ruby runtime, ExecArg sargp) {
}
static int run_exec_dup2(Ruby runtime, RubyArray ary, ExecArg eargp, ExecArg sargp, String[] errmsg) {
int n, i;
int ret;
int extra_fd = -1;
run_exec_dup2_fd_pair[] pairs = eargp.dup2_tmpbuf;
n = ary.size();
for (i = 0; i < n; i++) {
IRubyObject elt = ary.eltOk(i);
pairs[i].oldfd = RubyNumeric.fix2int(((RubyArray)elt).eltOk(1));
pairs[i].newfd = RubyNumeric.fix2int(((RubyArray)elt).eltOk(0));
pairs[i].older_index = -1;
}
if (sargp == null)
Arrays.sort(pairs, intcmp);
else
Arrays.sort(pairs, intrcmp);
for (i = 0; i < n; i++) {
int newfd = pairs[i].newfd;
run_exec_dup2_fd_pair key = new run_exec_dup2_fd_pair();
key.oldfd = newfd;
int found = Arrays.binarySearch(pairs, key, intcmp);
pairs[i].num_newer = 0;
if (found >= 0) {
while (found > 0 && pairs[found-1].oldfd == newfd) found--;
while (found < n && pairs[found].oldfd == newfd) {
pairs[i].num_newer++;
pairs[found].older_index = i;
found++;
}
}
}
for (i = 0; i < n; i++) {
int j = i;
while (j != -1 && pairs[j].oldfd != -1 && pairs[j].num_newer == 0) {
if (saveRedirectFd(runtime, pairs[j].newfd, sargp, errmsg) < 0)
return -1;
redirectDup2(eargp, pairs[j].oldfd, pairs[j].newfd);
pairs[j].oldfd = -1;
j = (int) pairs[j].older_index;
if (j != -1) pairs[j].num_newer--;
}
}
for (i = 0; i < n; i++) {
int j;
if (pairs[i].oldfd == -1)
continue;
if (pairs[i].oldfd == pairs[i].newfd) {
int fd = pairs[i].oldfd;
ret = runtime.getPosix().fcntl(fd, Fcntl.F_GETFD);
if (ret == -1) {
if (errmsg != null) errmsg[0] = "fcntl(F_GETFD)";
return -1;
}
if ((ret & FcntlLibrary.FD_CLOEXEC) != 0) {
ret &= ~FcntlLibrary.FD_CLOEXEC;
ret = runtime.getPosix().fcntlInt(fd, Fcntl.F_SETFD, ret);
if (ret == -1) {
if (errmsg != null) errmsg[0] = "fcntl(F_SETFD)";
return -1;
}
}
pairs[i].oldfd = -1;
continue;
}
if (extra_fd == -1) {
extra_fd = redirectDup(runtime, pairs[i].oldfd);
if (extra_fd == -1) {
if (errmsg != null) errmsg[0] = "dup";
return -1;
}
}
else {
redirectDup2(eargp, pairs[i].oldfd, extra_fd);
}
pairs[i].oldfd = extra_fd;
j = pairs[i].older_index;
pairs[i].older_index = -1;
while (j != -1) {
redirectDup2(eargp, pairs[j].oldfd, pairs[j].newfd);
pairs[j].oldfd = -1;
j = pairs[j].older_index;
}
}
if (extra_fd != -1) {
ret = redirectClose(runtime, eargp, extra_fd, sargp != null);
if (ret == -1) {
if (errmsg != null) errmsg[0] = "close";
return -1;
}
}
return 0;
}
static int redirectDup(Ruby runtime, int oldfd)
{
int ret;
ret = runtime.getPosix().dup(oldfd);
int flags = runtime.getPosix().fcntl(ret, Fcntl.F_GETFD);
runtime.getPosix().fcntlInt(ret, Fcntl.F_SETFD, flags | FcntlLibrary.FD_CLOEXEC);
return ret;
}
static int redirectCloexecDup(Ruby runtime, int oldfd)
{
int ret = redirectDup(runtime, oldfd);
int flags = runtime.getPosix().fcntl(ret, Fcntl.F_GETFD);
runtime.getPosix().fcntlInt(ret, Fcntl.F_SETFD, flags | FcntlLibrary.FD_CLOEXEC);
return ret;
}
static void redirectDup2(ExecArg eargp, int oldfd, int newfd)
{
eargp.fileActions.add(SpawnFileAction.dup(oldfd, newfd));
}
static int redirectClose(Ruby runtime, ExecArg eargp, int fd, boolean forChild)
{
if (forChild) {
eargp.fileActions.add(SpawnFileAction.close(fd));
return 0;
} else {
return runtime.getPosix().close(fd);
}
}
static void redirectOpen(ExecArg eargp, int fd, String pathname, int flags, int perm)
{
eargp.fileActions.add(SpawnFileAction.open(pathname, fd, flags, perm));
}
static int saveRedirectFd(Ruby runtime, int fd, ExecArg sargp, String[] errmsg) {
if (false && sargp != null) {
RubyArray newary;
int save_fd = redirectCloexecDup(runtime, fd);
if (save_fd == -1) {
if (runtime.getPosix().errno() == Errno.EBADF.intValue())
return 0;
if (errmsg != null) errmsg[0] = "dup";
return -1;
}
newary = sargp.fd_dup2;
if (newary == null) {
newary = runtime.newArray();
sargp.fd_dup2 = newary;
}
newary.push(runtime.newArray(runtime.newFixnum(fd), runtime.newFixnum(save_fd)));
newary = sargp.fd_close;
if (newary == null) {
newary = runtime.newArray();
sargp.fd_close = newary;
}
newary.push(runtime.newArray(runtime.newFixnum(save_fd), runtime.getNil()));
}
return 0;
}
int execargRunOptions(ThreadContext context, Ruby runtime, ExecArg eargp, ExecArg sargp, String[] errmsg) {
IRubyObject obj;
if (sargp != null) {
sargp.redirect_fds = context.nil;
}
if (eargp.pgroup_given()) {
if (run_exec_pgroup(runtime, eargp, sargp, errmsg) == -1)
return -1;
}
obj = eargp.rlimit_limits;
if (obj != null) {
throw runtime.newNotImplementedError("setting rlimit in child is unsupported");
}
boolean clearEnv = false;
if (eargp.unsetenv_others_given() && eargp.unsetenv_others_do()) {
throw runtime.newNotImplementedError("clearing env in child is not supported");
}
RubyArray env = eargp.env_modification;
if (env != null) {
eargp.envp_str = ShellLauncher.getModifiedEnv(runtime, env, clearEnv);
}
if (eargp.umask_given()) {
throw runtime.newNotImplementedError("setting umask in child is unsupported");
}
obj = eargp.fd_dup2;
if (obj != null) {
if (run_exec_dup2(runtime, (RubyArray)obj, eargp, sargp, errmsg) == -1)
return -1;
}
obj = eargp.fd_close;
if (obj != null) {
if (sargp != null)
runtime.getWarnings().warn("cannot close fd before spawn");
else {
if (run_exec_close(runtime, (RubyArray)obj, eargp, errmsg) == -1)
return -1;
}
}
obj = eargp.fd_dup2_child;
if (obj != null) {
if (run_exec_dup2_child(runtime, (RubyArray)obj, eargp, sargp, errmsg) == -1)
return -1;
}
if (eargp.chdir_given()) {
throw new RuntimeException("BUG: chdir not supported in posix_spawn; should have been made into chdir");
}
if (eargp.gid_given()) {
throw runtime.newNotImplementedError("setgid in the child is not supported");
}
if (eargp.uid_given()) {
throw runtime.newNotImplementedError("setuid in the child is not supported");
}
return 0;
}
static int run_exec_close(Ruby runtime, RubyArray ary, ExecArg eargp, String[] errmsg) {
long i;
int ret;
for (i = 0; i < ary.size(); i++) {
RubyArray elt = (RubyArray)ary.eltOk(i);
int fd = RubyNumeric.fix2int(elt.eltOk(0));
ret = redirectClose(runtime, eargp, fd, true);
if (ret == -1) {
if (errmsg != null) errmsg[0] = "close";
return -1;
}
}
return 0;
}
static int run_exec_dup2_child(Ruby runtime, RubyArray ary, ExecArg eargp, ExecArg sargp, String[] errmsg) {
long i;
int ret;
for (i = 0; i < ary.size(); i++) {
RubyArray elt = (RubyArray)ary.eltOk(i);
int newfd = RubyNumeric.fix2int(elt.eltOk(0));
int oldfd = RubyNumeric.fix2int(elt.eltOk(1));
redirectDup2(eargp, oldfd, newfd);
}
return 0;
}
private static class run_exec_dup2_fd_pair {
int oldfd;
int newfd;
int older_index;
int num_newer;
};
static int runExecDup2TmpbufSize(int n) {
return n;
}
static void execargFixup(ThreadContext context, Ruby runtime, ExecArg eargp) {
execargParentStart(context, runtime, eargp);
}
static void execargParentStart(ThreadContext context, Ruby runtime, ExecArg eargp) {
try {
execargParentStart1(context, runtime, eargp);
} catch (RaiseException re) {
execargParentEnd(runtime, eargp);
throw re;
}
}
static void execargParentStart1(ThreadContext context, Ruby runtime, ExecArg eargp) {
boolean unsetenv_others;
RubyArray envopts;
RubyArray<RubyArray> ary;
eargp.redirect_fds = checkExecFds(context, runtime, eargp);
ary = eargp.fd_open;
if (ary != null) {
long i;
for (i = 0; i < ary.size(); i++) {
RubyArray<RubyArray> elt = ary.eltOk(i);
int fd = RubyNumeric.fix2int(elt.eltOk(0));
RubyArray param = elt.eltOk(1);
IRubyObject vpath = param.eltOk(0);
int flags = RubyNumeric.num2int(param.eltOk(1));
int perm = RubyNumeric.num2int(param.eltOk(2));
IRubyObject fd2v = param.entry(3);
int fd2;
if (fd2v.isNil()) {
RubyIO.Sysopen open_data = new RubyIO.Sysopen();
vpath = RubyFile.get_path(context, vpath);
while (true) {
open_data.fname = vpath.toString();
open_data.oflags = flags;
open_data.perm = perm;
ChannelFD ret;
open_data.errno = Errno.EINTR;
ret = open_func(runtime, open_data);
if (ret == null) {
if (open_data.errno == Errno.EINTR) {
context.pollThreadEvents();
continue;
}
runtime.newErrnoFromInt(open_data.errno.intValue(), vpath.toString());
}
fd2 = ((ChannelFD) ret).realFileno;
param.store(3, runtime.newFixnum(fd2));
context.pollThreadEvents();
break;
}
}
else {
fd2 = RubyNumeric.num2int(fd2v);
}
execargAddopt(context, runtime, eargp, runtime.newFixnum(fd), runtime.newFixnum(fd2));
}
}
ary = eargp.fd_dup2;
if (ary != null) {
int len = runExecDup2TmpbufSize(ary.size());
run_exec_dup2_fd_pair[] tmpbuf = new run_exec_dup2_fd_pair[len];
for (int i = 0; i < tmpbuf.length; i++) tmpbuf[i] = new run_exec_dup2_fd_pair();
eargp.dup2_tmpbuf = tmpbuf;
}
IRubyObject envtbl;
unsetenv_others = eargp.unsetenv_others_given() && eargp.unsetenv_others_do();
envopts = eargp.env_modification;
if (unsetenv_others || envopts != null) {
if (unsetenv_others) {
envtbl = RubyHash.newHash(runtime);
}
else {
envtbl = runtime.getObject().getConstant("ENV");
envtbl = TypeConverter.convertToType(envtbl, runtime.getHash(), "to_hash").dup();
}
if (envopts != null) {
RubyHash stenv = (RubyHash)envtbl;
long i;
for (i = 0; i < envopts.size(); i++) {
IRubyObject pair = envopts.eltOk(i);
IRubyObject key = ((RubyArray)pair).eltOk(0);
IRubyObject val = ((RubyArray)pair).eltOk(1);
if (val.isNil()) {
IRubyObject stkey = key;
stenv.fastDelete(stkey);
}
else {
stenv.op_aset(context, key, val);
}
}
}
} else {
envtbl = runtime.getObject().getConstant("ENV");
envtbl = TypeConverter.convertToType(envtbl, runtime.getHash(), "to_hash");
}
buildEnvp(runtime, eargp, envtbl);
}
static void execargParentEnd(Ruby runtime, ExecArg eargp) {
int err = runtime.getPosix().errno();
RubyArray<RubyArray> ary;
ary = eargp.fd_open;
if (ary != null) {
long i;
for (i = 0; i < ary.size(); i++) {
RubyArray<RubyArray> elt = ary.eltOk(i);
RubyArray param = elt.eltOk(1);
IRubyObject fd2v;
int fd2;
fd2v = param.entry(3);
if (!fd2v.isNil()) {
fd2 = RubyNumeric.fix2int(fd2v);
parentRedirectClose(runtime, fd2);
param.store(3, runtime.getNil());
}
}
}
runtime.getPosix().errno(err);
}
static ChannelFD open_func(Ruby runtime, RubyIO.Sysopen data) {
ChannelFD ret = parentRedirectOpen(runtime, data);
data.errno = Errno.valueOf(runtime.getPosix().errno());
return ret;
}
static ChannelFD parentRedirectOpen(Ruby runtime, RubyIO.Sysopen data) {
return RubyIO.cloexecOpen(runtime, data);
}
static void parentRedirectClose(Ruby runtime, int fd) {
if (fd > 2) runtime.getPosix().close(fd);
}
private static void buildEnvp(Ruby runtime, ExecArg eargp, IRubyObject envtbl) {
String[] envp_str;
List<String> envp_buf;
envp_buf = new ArrayList();
for (Map.Entry<IRubyObject, IRubyObject> entry : (Set<Map.Entry<IRubyObject, IRubyObject>>)((RubyHash)envtbl).directEntrySet()) {
IRubyObject key = entry.getKey();
IRubyObject val = entry.getValue();
envp_buf.add(StringSupport.checkEmbeddedNulls(runtime, key).toString()
+ "="
+ StringSupport.checkEmbeddedNulls(runtime, val));
}
envp_str = new String[envp_buf.size()];
envp_buf.toArray(envp_str);
eargp.envp_str = envp_str;
eargp.envp_buf = envp_buf;
}
static int checkExecFds1(ThreadContext context, Ruby runtime, ExecArg eargp, RubyHash h, int maxhint, IRubyObject ary) {
long i;
if (ary != null) {
for (i = 0; i < ((RubyArray)ary).size(); i++) {
IRubyObject elt = ((RubyArray)ary).eltOk(i);
int fd = RubyNumeric.fix2int(((RubyArray)elt).eltOk(0));
if (h.fastARef(runtime.newFixnum(fd)) != null) {
throw runtime.newArgumentError("fd " + fd + " specified twice");
}
if (ary == eargp.fd_open || ary == eargp.fd_dup2)
h.op_aset(context, runtime.newFixnum(fd), runtime.getTrue());
else if (ary == eargp.fd_dup2_child)
h.op_aset(context, runtime.newFixnum(fd), ((RubyArray)elt).eltOk(1));
else
h.op_aset(context, runtime.newFixnum(fd), runtime.newFixnum(-1));
if (maxhint < fd)
maxhint = fd;
if (ary == eargp.fd_dup2 || ary == eargp.fd_dup2_child) {
fd = RubyNumeric.fix2int(((RubyArray)elt).eltOk(1));
if (maxhint < fd)
maxhint = fd;
}
}
}
return maxhint;
}
static IRubyObject checkExecFds(ThreadContext context, Ruby runtime, ExecArg eargp) {
RubyHash h = RubyHash.newHash(runtime);
IRubyObject ary;
int maxhint = -1;
long i;
maxhint = checkExecFds1(context, runtime, eargp, h, maxhint, eargp.fd_dup2);
maxhint = checkExecFds1(context, runtime, eargp, h, maxhint, eargp.fd_close);
maxhint = checkExecFds1(context, runtime, eargp, h, maxhint, eargp.fd_open);
maxhint = checkExecFds1(context, runtime, eargp, h, maxhint, eargp.fd_dup2_child);
if (eargp.fd_dup2_child != null) {
ary = eargp.fd_dup2_child;
for (i = 0; i < ((RubyArray)ary).size(); i++) {
IRubyObject elt = ((RubyArray)ary).eltOk(i);
int newfd = RubyNumeric.fix2int(((RubyArray)elt).eltOk(0));
int oldfd = RubyNumeric.fix2int(((RubyArray)elt).eltOk(1));
int lastfd = oldfd;
IRubyObject val = h.fastARef(runtime.newFixnum(lastfd));
long depth = 0;
while (val instanceof RubyFixnum && 0 <= ((RubyFixnum)val).getIntValue()) {
lastfd = RubyNumeric.fix2int(val);
val = h.fastARef(val);
if (((RubyArray)ary).size() < depth)
throw runtime.newArgumentError("cyclic child fd redirection from " + oldfd);
depth++;
}
if (val != runtime.getTrue())
throw runtime.newArgumentError("child fd " + oldfd + " is not redirected");
if (oldfd != lastfd) {
IRubyObject val2;
((RubyArray)elt).store(1, runtime.newFixnum(lastfd));
h.op_aset(context, runtime.newFixnum(newfd), runtime.newFixnum(lastfd));
val = runtime.newFixnum(oldfd);
while ((val2 = h.fastARef(val)) instanceof RubyFixnum) {
h.op_aset(context, val, runtime.newFixnum(lastfd));
val = val2;
}
}
}
}
eargp.close_others_maxhint = maxhint;
return h;
}
static int execargAddopt(ThreadContext context, Ruby runtime, ExecArg eargp, IRubyObject key, IRubyObject val) {
String id;
int rtype;
boolean redirect = false;
switch (key.getType().getClassIndex()) {
case SYMBOL:
id = key.toString();
if (id.equals("pgroup")) {
long pgroup;
if (eargp.pgroup_given()) {
throw runtime.newArgumentError("pgroup option specified twice");
}
if (val == null || !val.isTrue())
pgroup = -1;
else if (val == runtime.getTrue())
pgroup = 0;
else {
pgroup = val.convertToInteger().getLongValue();
if (pgroup < 0) {
throw runtime.newArgumentError("negative process group symbol : " + pgroup);
}
}
eargp.pgroup_given_set();
eargp.pgroup_pgid = pgroup;
}
else
if (id.startsWith("rlimit_") &&
false) {
IRubyObject ary = eargp.rlimit_limits;
IRubyObject tmp, softlim, hardlim;
if (eargp.rlimit_limits == null)
ary = eargp.rlimit_limits = runtime.newArray();
else
ary = eargp.rlimit_limits;
tmp = TypeConverter.checkArrayType(runtime, val);
if (!tmp.isNil()) {
if (((RubyArray)tmp).size() == 1)
softlim = hardlim = ((RubyArray)tmp).eltOk(0).convertToInteger();
else if (((RubyArray)tmp).size() == 2) {
softlim = ((RubyArray)tmp).eltOk(0).convertToInteger();
hardlim = ((RubyArray)tmp).eltOk(1).convertToInteger();
}
else {
throw runtime.newArgumentError("wrong exec rlimit option");
}
}
else {
softlim = hardlim = val.convertToInteger();
}
tmp = RubyArray.newArray(runtime, runtime.newFixnum(rtype), softlim, hardlim);
((RubyArray)ary).push(tmp);
}
else
if (id.equals("unsetenv_others")) {
if (eargp.unsetenv_others_given()) {
throw runtime.newArgumentError("unsetenv_others option specified twice");
}
eargp.unsetenv_others_given_set();
if (!val.isNil()) {
eargp.unsetenv_others_do_set();
} else {
eargp.unsetenv_others_do_clear();
}
}
else if (id.equals("chdir")) {
if (eargp.chdir_given()) {
throw runtime.newArgumentError("chdir option specified twice");
}
RubyString valTmp = RubyFile.get_path(context, val);
eargp.chdir_given_set();
eargp.chdir_dir = valTmp.toString();
}
else if (id.equals("umask")) {
int cmask = val.convertToInteger().getIntValue();
if (eargp.umask_given()) {
throw runtime.newArgumentError("umask option specified twice");
}
eargp.umask_given_set();
eargp.umask_mask = cmask;
}
else if (id.equals("close_others")) {
if (eargp.close_others_given()) {
throw runtime.newArgumentError("close_others option specified twice");
}
eargp.close_others_given_set();
if (!val.isNil()) {
eargp.close_others_do_set();
} else {
eargp.close_others_do_clear();
}
}
else if (id.equals("in")) {
key = RubyFixnum.zero(runtime);
checkExecRedirect(context, runtime, key, val, eargp);
}
else if (id.equals("out")) {
key = RubyFixnum.one(runtime);
checkExecRedirect(context, runtime, key, val, eargp);
}
else if (id.equals("err")) {
key = RubyFixnum.two(runtime);
checkExecRedirect(context, runtime, key, val, eargp);
}
else if (id.equals("uid") && false) {
if (eargp.uid_given()) {
throw runtime.newArgumentError("uid option specified twice");
}
{
eargp.uid = val.convertToInteger().getIntValue();
eargp.uid_given_set();
}
}
else if (id.equals("gid") && false) {
if (eargp.gid_given()) {
throw runtime.newArgumentError("gid option specified twice");
}
{
eargp.gid = val.convertToInteger().getIntValue();
eargp.gid_given_set();
}
}
else {
return ST_STOP;
}
break;
case INTEGER:
if (!(key instanceof RubyFixnum)) {
return ST_STOP;
}
case FILE:
case IO:
case ARRAY:
checkExecRedirect(context, runtime, key, val, eargp);
break;
default:
return ST_STOP;
}
return ST_CONTINUE;
}
static void checkExecRedirect(ThreadContext context, Ruby runtime, IRubyObject key, IRubyObject val, ExecArg eargp) {
IRubyObject param;
IRubyObject path, flags, perm;
IRubyObject tmp;
String id;
switch (val.getMetaClass().getRealClass().getClassIndex()) {
case SYMBOL:
id = val.toString();
if (id.equals("close")) {
param = context.nil;
eargp.fd_close = checkExecRedirect1(runtime, eargp.fd_close, key, param);
}
else if (id.equals("in")) {
param = runtime.newFixnum(0);
eargp.fd_dup2 = checkExecRedirect1(runtime, eargp.fd_dup2, key, param);
}
else if (id.equals("out")) {
param = runtime.newFixnum(1);
eargp.fd_dup2 = checkExecRedirect1(runtime, eargp.fd_dup2, key, param);
}
else if (id.equals("err")) {
param = runtime.newFixnum(2);
eargp.fd_dup2 = checkExecRedirect1(runtime, eargp.fd_dup2, key, param);
}
else {
throw runtime.newArgumentError("wrong exec redirect symbol: " + id);
}
break;
case FILE:
case IO:
val = checkExecRedirectFd(runtime, val, false);
case INTEGER:
if (val instanceof RubyFixnum) {
param = val;
eargp.fd_dup2 = checkExecRedirect1(runtime, eargp.fd_dup2, key, param);
break;
}
checkExecRedirectDefault(runtime, key, val, eargp);
break;
case ARRAY:
path = ((RubyArray)val).eltOk(0);
if (((RubyArray)val).size() == 2 && path instanceof RubySymbol &&
path.toString().equals("child")) {
param = checkExecRedirectFd(runtime, ((RubyArray)val).eltOk(1), false);
eargp.fd_dup2_child = checkExecRedirect1(runtime, eargp.fd_dup2_child, key, param);
}
else {
path = RubyFile.get_path(context, path);
flags = ((RubyArray)val).eltOk(1);
int intFlags;
if (flags.isNil())
intFlags = OpenFlags.O_RDONLY.intValue();
else if (flags instanceof RubyString)
intFlags = OpenFile.ioModestrOflags(runtime, flags.toString());
else
intFlags = flags.convertToInteger().getIntValue();
flags = runtime.newFixnum(intFlags);
perm = ((RubyArray)val).entry(2);
perm = perm.isNil() ? runtime.newFixnum(0644) : perm.convertToInteger();
param = RubyArray.newArray(runtime,
((RubyString)path).strDup(runtime).export(context),
flags,
perm);
eargp.fd_open = checkExecRedirect1(runtime, eargp.fd_open, key, param);
}
break;
case STRING:
path = val;
path = RubyFile.get_path(context, path);
if (key instanceof RubyIO)
key = checkExecRedirectFd(runtime, key, true);
if (key instanceof RubyFixnum && (((RubyFixnum)key).getIntValue() == 1 || ((RubyFixnum)key).getIntValue() == 2))
flags = runtime.newFixnum(OpenFlags.O_WRONLY.intValue()|OpenFlags.O_CREAT.intValue()|OpenFlags.O_TRUNC.intValue());
else
flags = runtime.newFixnum(OpenFlags.O_RDONLY.intValue());
perm = runtime.newFixnum(0644);
param = RubyArray.newArray(runtime,
((RubyString)path).strDup(runtime).export(context),
flags,
perm);
eargp.fd_open = checkExecRedirect1(runtime, eargp.fd_open, key, param);
break;
default:
checkExecRedirectDefault(runtime, key, val, eargp);
}
}
private static void checkExecRedirectDefault(Ruby runtime, IRubyObject key, IRubyObject val, ExecArg eargp) {
IRubyObject tmp;
IRubyObject param;
tmp = val;
val = TypeConverter.ioCheckIO(runtime, tmp);
if (!val.isNil()) {
val = checkExecRedirectFd(runtime, val, false);
param = val;
eargp.fd_dup2 = checkExecRedirect1(runtime, eargp.fd_dup2, key, param);
}
throw runtime.newArgumentError("wrong exec redirect action");
}
static IRubyObject checkExecRedirectFd(Ruby runtime, IRubyObject v, boolean iskey) {
IRubyObject tmp;
int fd;
if (v instanceof RubyFixnum) {
fd = RubyNumeric.fix2int(v);
}
else if (v instanceof RubySymbol) {
String id = v.toString();
if (id.equals("in"))
fd = 0;
else if (id.equals("out"))
fd = 1;
else if (id.equals("err"))
fd = 2;
else
throw runtime.newArgumentError("wrong exec redirect");
}
else if (!(tmp = TypeConverter.convertToTypeWithCheck(v, runtime.getIO(), "to_io")).isNil()) {
OpenFile fptr;
fptr = ((RubyIO)tmp).getOpenFileChecked();
if (fptr.tiedIOForWriting != null)
throw runtime.newArgumentError("duplex IO redirection");
fd = fptr.fd().bestFileno();
}
else {
throw runtime.newArgumentError("wrong exec redirect");
}
if (fd < 0) {
throw runtime.newArgumentError("negative file descriptor");
}
else if (Platform.IS_WINDOWS && fd >= 3 && iskey) {
throw runtime.newArgumentError("wrong file descriptor (" + fd + ")");
}
return runtime.newFixnum(fd);
}
static RubyArray checkExecRedirect1(Ruby runtime, RubyArray ary, IRubyObject key, IRubyObject param) {
if (ary == null) {
ary = runtime.newArray();
}
if (!(key instanceof RubyArray)) {
IRubyObject fd = checkExecRedirectFd(runtime, key, !param.isNil());
ary.push(runtime.newArray(fd, param));
}
else {
int i, n=0;
for (i = 0 ; i < ((RubyArray)key).size(); i++) {
IRubyObject v = ((RubyArray)key).eltOk(i);
IRubyObject fd = checkExecRedirectFd(runtime, v, !param.isNil());
ary.push(runtime.newArray(fd, param));
n++;
}
}
return ary;
}
private static final int ST_CONTINUE = 0;
private static final int ST_STOP = 1;
public static ExecArg execargNew(ThreadContext context, IRubyObject[] argv, boolean accept_shell) {
ExecArg eargp = new ExecArg();
execargInit(context, argv, accept_shell, eargp);
return eargp;
}
private static RubyString execargInit(ThreadContext context, IRubyObject[] argv, boolean accept_shell, ExecArg eargp) {
RubyString prog, ret;
IRubyObject[] env_opt = {context.nil, context.nil};
IRubyObject[][] argv_p = {argv};
prog = execGetargs(context, argv_p, accept_shell, env_opt);
execFillarg(context, prog, argv_p[0], env_opt[0], env_opt[1], eargp);
ret = eargp.use_shell ? eargp.command_name : eargp.command_name;
return ret;
}
private static RubyString execGetargs(ThreadContext context, IRubyObject[][] argv_p, boolean accept_shell, IRubyObject[] env_opt) {
Ruby runtime = context.runtime;
IRubyObject hash;
RubyString prog;
int beg = 0;
int end = argv_p[0].length;
if (end >= 1) {
hash = TypeConverter.checkHashType(runtime, argv_p[0][end - 1]);
if (!hash.isNil()) {
env_opt[1] = hash;
end--;
}
}
if (end >= 1) {
hash = TypeConverter.checkHashType(runtime, argv_p[0][0]);
if (!hash.isNil()) {
env_opt[0] = hash;
beg++;
}
}
argv_p[0] = Arrays.copyOfRange(argv_p[0], beg, end);
prog = checkArgv(context, argv_p[0]);
if (prog == null) {
prog = (RubyString)argv_p[0][0];
if (accept_shell && (end - beg) == 1) {
argv_p[0] = IRubyObject.NULL_ARRAY;
}
}
return prog;
}
public static RubyString checkArgv(ThreadContext context, IRubyObject[] argv) {
Ruby runtime = context.runtime;
IRubyObject tmp;
RubyString prog;
int i;
Arity.checkArgumentCount(runtime, argv, 1, Integer.MAX_VALUE);
prog = null;
tmp = TypeConverter.checkArrayType(runtime, argv[0]);
if (!tmp.isNil()) {
if (((RubyArray)tmp).size() != 2) {
throw runtime.newArgumentError("wrong first argument");
}
prog = ((RubyArray)tmp).eltOk(0).convertToString();
argv[0] = ((RubyArray)tmp).eltOk(1);
StringSupport.checkEmbeddedNulls(runtime, prog);
prog = prog.strDup(runtime);
prog.setFrozen(true);
}
for (i = 0; i < argv.length; i++) {
argv[i] = argv[i].convertToString();
argv[i] = ((RubyString)argv[i]).newFrozen();
StringSupport.checkEmbeddedNulls(runtime, argv[i]);
}
return prog;
}
private static final int posix_sh_cmd_length = 8;
private static final String posix_sh_cmds[] = {
"!",
".",
":",
"break",
"case",
"continue",
"do",
"done",
"elif",
"else",
"esac",
"eval",
"exec",
"exit",
"export",
"fi",
"for",
"if",
"in",
"readonly",
"return",
"set",
"shift",
"then",
"times",
"trap",
"unset",
"until",
"while",
};
private static final byte[] DUMMY_ARRAY = ByteList.NULL_ARRAY;
private static void execFillarg(ThreadContext context, RubyString prog, IRubyObject[] argv, IRubyObject env, IRubyObject opthash, ExecArg eargp) {
Ruby runtime = context.runtime;
int argc = argv.length;
if (!opthash.isNil()) {
checkExecOptions(context, runtime, (RubyHash)opthash, eargp);
}
String virtualCWD = runtime.getCurrentDirectory();
if (!virtualCWD.equals(runtime.getPosix().getcwd())) {
String arg = prog.toString();
if ((arg = ShellLauncher.changeDirInsideJar(runtime, arg)) != null) {
prog = RubyString.newString(runtime, arg);
} else if (virtualCWD.startsWith("uri:classloader:")) {
} else if (!eargp.chdir_given()) {
eargp.chdir_given_set();
eargp.chdir_dir = virtualCWD;
}
}
if (eargp.chdir_given() && argc > 1) {
RubyArray array = RubyArray.newArrayMayCopy(runtime, argv);
prog = (RubyString)array.join(context, RubyString.newString(runtime, " "));
}
if (!env.isNil()) {
eargp.env_modification = checkExecEnv(context, (RubyHash) env, eargp);
}
prog = prog.export(context);
eargp.use_shell = argc == 0 || eargp.chdir_given();
if (eargp.use_shell)
eargp.command_name = prog;
else
eargp.command_name = prog;
if (!Platform.IS_WINDOWS) {
if (eargp.use_shell) {
byte[] pBytes;
int p;
ByteList first = new ByteList(DUMMY_ARRAY, false);
boolean has_meta = false;
ByteList progByteList = prog.getByteList();
pBytes = progByteList.unsafeBytes();
for (p = 0; p < progByteList.length(); p++){
if (progByteList.get(p) == ' ' || progByteList.get(p) == '\t'){
if (first.unsafeBytes() != DUMMY_ARRAY && first.length() == 0) first.setRealSize(p - first.begin());
}
else{
if (first.unsafeBytes() == DUMMY_ARRAY) { first.setUnsafeBytes(pBytes); first.setBegin(p + progByteList.begin()); }
}
if (!has_meta && "*?{}[]<>()~&|\\$;'`\"\n#".indexOf(progByteList.get(p) & 0xFF) != -1)
has_meta = true;
if (first.length() == 0) {
if (progByteList.get(p) == '='){
has_meta = true;
}
else if (progByteList.get(p) == '/'){
first.setRealSize(0x100);
}
}
if (has_meta)
break;
}
if (!has_meta && first.getUnsafeBytes() != DUMMY_ARRAY) {
if (first.length() == 0) first.setRealSize(p - first.getBegin());
if (first.length() > 0 && first.length() <= posix_sh_cmd_length &&
Arrays.binarySearch(posix_sh_cmds, first.toString(), StringComparator.INSTANCE) >= 0)
has_meta = true;
}
if (!has_meta && !eargp.chdir_given()) {
eargp.use_shell = false;
}
if (!eargp.use_shell) {
List<byte[]> argv_buf = new ArrayList<>();
pBytes = prog.getByteList().unsafeBytes();
p = prog.getByteList().begin();
int pEnd = prog.getByteList().length() + p;
while (p < pEnd){
while (p < pEnd && (pBytes[p] == ' ' || pBytes[p] == '\t'))
p++;
if (p < pEnd){
int w = p;
while (p < pEnd && pBytes[p] != ' ' && pBytes[p] != '\t')
p++;
argv_buf.add(Arrays.copyOfRange(pBytes, w, p));
eargp.argv_buf = argv_buf;
}
}
if (argv_buf.size() > 0) {
eargp.command_name = RubyString.newStringNoCopy(runtime, argv_buf.get(0));
} else {
eargp.command_name = RubyString.newEmptyString(runtime);
}
}
}
}
if (!eargp.use_shell) {
String abspath;
abspath = dlnFindExeR(runtime, eargp.command_name.toString(), eargp.path_env);
if (abspath != null)
eargp.command_abspath = StringSupport.checkEmbeddedNulls(runtime, RubyString.newString(runtime, abspath));
else
eargp.command_abspath = null;
}
if (!eargp.use_shell && eargp.argv_buf == null) {
int i;
ArrayList<byte[]> argv_buf = new ArrayList<>(argc);
for (i = 0; i < argc; i++) {
IRubyObject arg = argv[i];
RubyString argStr = StringSupport.checkEmbeddedNulls(runtime, arg);
argStr = argStr.export(context);
argv_buf.add(argStr.getBytes());
}
eargp.argv_buf = argv_buf;
}
if (!eargp.use_shell) {
ArgvStr argv_str = new ArgvStr();
argv_str.argv = new String[eargp.argv_buf.size()];
int i = 0;
for (byte[] bytes : eargp.argv_buf) {
argv_str.argv[i++] = new String(bytes);
}
eargp.argv_str = argv_str;
}
}
private static final class StringComparator implements Comparator<String> {
static final StringComparator INSTANCE = new StringComparator();
public int compare(String o1, String o2) {
int ret = o1.compareTo(o2);
if (ret == 0 && o1.length() > o2.length()) return -1;
return ret;
}
}
private static String dlnFindExeR(Ruby runtime, String fname, IRubyObject path) {
File exePath = ShellLauncher.findPathExecutable(runtime, fname, path);
return exePath != null ? exePath.getAbsolutePath() : null;
}
private static class ArgvStr {
String[] argv;
}
public static class ExecArg {
boolean use_shell;
RubyString command_name;
RubyString command_abspath;
ArgvStr argv_str;
List<byte[]> argv_buf;
IRubyObject redirect_fds;
String[] envp_str;
List<String> envp_buf;
run_exec_dup2_fd_pair[] dup2_tmpbuf;
int flags;
long pgroup_pgid = -1;
IRubyObject rlimit_limits;
int umask_mask;
int uid;
int gid;
RubyArray fd_dup2;
RubyArray fd_close;
RubyArray<RubyArray> fd_open;
RubyArray fd_dup2_child;
int close_others_maxhint;
RubyArray env_modification;
String chdir_dir;
List<SpawnFileAction> fileActions = new ArrayList();
List<SpawnAttribute> attributes = new ArrayList();
IRubyObject path_env;
boolean pgroup_given() {
return (flags & 0x1) != 0;
}
boolean umask_given() {
return (flags & 0x2) != 0;
}
boolean unsetenv_others_given() {
return (flags & 0x4) != 0;
}
boolean unsetenv_others_do() {
return (flags & 0x8) != 0;
}
boolean close_others_given() {
return (flags & 0x10) != 0;
}
boolean close_others_do() {
return (flags & 0x20) != 0;
}
boolean chdir_given() {
return (flags & 0x40) != 0;
}
boolean new_pgroup_given() {
return (flags & 0x80) != 0;
}
boolean new_pgroup_flag() {
return (flags & 0x100) != 0;
}
boolean uid_given() {
return (flags & 0x200) != 0;
}
boolean gid_given() {
return (flags & 0x400) != 0;
}
void pgroup_given_set() {
flags |= 0x1;
}
void umask_given_set() {
flags |= 0x2;
}
void unsetenv_others_given_set() {
flags |= 0x4;
}
void unsetenv_others_do_set() {
flags |= 0x8;
}
void close_others_given_set() {
flags |= 0x10;
}
void close_others_do_set() {
flags |= 0x20;
}
void chdir_given_set() {
flags |= 0x40;
}
void new_pgroup_given_set() {
flags |= 0x80;
}
void new_pgroup_flag_set() {
flags |= 0x100;
}
void uid_given_set() {
flags |= 0x200;
}
void gid_given_set() {
flags |= 0x400;
}
void pgroup_given_clear() {
flags &= ~0x1;
}
void umask_given_clear() {
flags &= ~0x2;
}
void unsetenv_others_given_clear() {
flags &= ~0x4;
}
void unsetenv_others_do_clear() {
flags &= ~0x8;
}
void close_others_given_clear() {
flags &= ~0x10;
}
void close_others_do_clear() {
flags &= ~0x20;
}
void chdir_given_clear() {
flags &= ~0x40;
}
void new_pgroup_given_clear() {
flags &= ~0x80;
}
void new_pgroup_flag_clear() {
flags &= ~0x100;
}
void uid_given_clear() {
flags &= ~0x200;
}
void gid_given_clear() {
flags &= ~0x400;
}
}
private static final Comparator<run_exec_dup2_fd_pair> intcmp = new Comparator<run_exec_dup2_fd_pair>() {
@Override
public int compare(run_exec_dup2_fd_pair o1, run_exec_dup2_fd_pair o2) {
return Integer.compare(o1.oldfd, o2.oldfd);
}
};
private static final Comparator<run_exec_dup2_fd_pair> intrcmp = new Comparator<run_exec_dup2_fd_pair>() {
@Override
public int compare(run_exec_dup2_fd_pair o1, run_exec_dup2_fd_pair o2) {
return Integer.compare(o2.oldfd, o1.oldfd);
}
};
}