Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jruby.util.ShellLauncher Maven / Gradle / Ivy
package org.jruby.util;
import static java.lang.System.out;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jruby.Main;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyHash;
import org.jruby.RubyIO;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyModule;
import org.jruby.RubyString;
import jnr.posix.util.FieldAccess;
import jnr.posix.util.Platform;
import org.jruby.runtime.Helpers;
import org.jruby.ext.rbconfig.RbConfigLibrary;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.cli.Options;
import org.jruby.util.io.IOOptions;
import org.jruby.util.io.ModeFlags;
@SuppressWarnings ("deprecation" )
public class ShellLauncher {
private static final boolean DEBUG = false ;
private static final String PATH_ENV = "PATH" ;
private static final String[] DEFAULT_PATH =
{ "/usr/local/bin" , "/usr/ucb" , "/usr/bin" , "/bin" };
private static final String[] WINDOWS_EXE_SUFFIXES =
{ ".exe" , ".com" , ".bat" , ".cmd" };
private static final String[] WINDOWS_INTERNAL_CMDS = {
"assoc" , "break" , "call" , "cd" , "chcp" ,
"chdir" , "cls" , "color" , "copy" , "ctty" , "date" , "del" , "dir" , "echo" , "endlocal" ,
"erase" , "exit" , "for" , "ftype" , "goto" , "if" , "lfnfor" , "lh" , "lock" , "md" , "mkdir" ,
"move" , "path" , "pause" , "popd" , "prompt" , "pushd" , "rd" , "rem" , "ren" , "rename" ,
"rmdir" , "set" , "setlocal" , "shift" , "start" , "time" , "title" , "truename" , "type" ,
"unlock" , "ver" , "verify" , "vol" , };
private static final Pattern SHELL_METACHARACTER_PATTERN =
Pattern.compile("[*?{}\\[\\]<>()~&|$;'`\\\\\"\\n]" );
private static final Pattern WIN_ENVVAR_PATTERN = Pattern.compile("%\\w+%" );
private static class ScriptThreadProcess extends Process implements Runnable {
private final String[] argArray;
private final String[] env;
private final File pwd;
private final boolean pipedStreams;
private final PipedInputStream processOutput;
private final PipedInputStream processError;
private final PipedOutputStream processInput;
private RubyInstanceConfig config;
private Thread processThread;
private int result;
private Ruby parentRuntime;
public ScriptThreadProcess (Ruby parentRuntime, final String[] argArray, final String[] env, final File dir) {
this (parentRuntime, argArray, env, dir, true );
}
public ScriptThreadProcess (Ruby parentRuntime, final String[] argArray, final String[] env, final File dir, final boolean pipedStreams) {
this .parentRuntime = parentRuntime;
this .argArray = argArray;
this .env = env;
this .pwd = dir;
this .pipedStreams = pipedStreams;
if (pipedStreams) {
processOutput = new PipedInputStream();
processError = new PipedInputStream();
processInput = new PipedOutputStream();
} else {
processOutput = processError = null ;
processInput = null ;
}
}
public void run () {
try {
this .result = (new Main(config).run(argArray)).getStatus();
} catch (Throwable throwable) {
throwable.printStackTrace(this .config.getError());
this .result = -1 ;
} finally {
this .config.getOutput().close();
this .config.getError().close();
try {this .config.getInput().close();} catch (IOException ioe) {}
}
}
private Map environmentMap (String[] env) {
Map m = new HashMap();
for (int i = 0 ; i < env.length; i++) {
String[] kv = env[i].split("=" , 2 );
m.put(kv[0 ], kv[1 ]);
}
return m;
}
public void start () throws IOException {
config = new RubyInstanceConfig(parentRuntime.getInstanceConfig());
config.setEnvironment(environmentMap(env));
config.setCurrentDirectory(pwd.toString());
if (pipedStreams) {
config.setInput(new PipedInputStream(processInput));
config.setOutput(new PrintStream(new PipedOutputStream(processOutput)));
config.setError(new PrintStream(new PipedOutputStream(processError)));
}
String procName = "piped" ;
if (argArray.length > 0 ) {
procName = argArray[0 ];
}
processThread = new Thread(this , "ScriptThreadProcess: " + procName);
processThread.setDaemon(true );
processThread.start();
}
public OutputStream getOutputStream () {
return processInput;
}
public InputStream getInputStream () {
return processOutput;
}
public InputStream getErrorStream () {
return processError;
}
public int waitFor () throws InterruptedException {
processThread.join();
return result;
}
public int exitValue () {
return result;
}
public void destroy () {
if (pipedStreams) {
closeStreams();
}
processThread.interrupt();
}
private void closeStreams () {
try { processInput.close(); } catch (IOException io) {}
try { processOutput.close(); } catch (IOException io) {}
try { processError.close(); } catch (IOException io) {}
}
}
public static String[] getCurrentEnv(Ruby runtime) {
return getCurrentEnv(runtime, null );
}
private static String[] getCurrentEnv(Ruby runtime, Map mergeEnv) {
ThreadContext context = runtime.getCurrentContext();
boolean traceEnabled = context.isEventHooksEnabled();
context.setEventHooksEnabled(false );
try {
RubyHash hash = (RubyHash)runtime.getObject().getConstant("ENV" ).dup();
String[] ret, ary;
if (mergeEnv != null && !mergeEnv.isEmpty()) {
ret = new String[hash.size() + mergeEnv.size()];
} else {
ret = new String[hash.size()];
}
int i=0 ;
for (Map.Entry e : (Set)hash.directEntrySet()) {
if (e.getKey() == null ) {
throw runtime.newTypeError(runtime.getNil(), runtime.getStructClass());
}
if (e.getValue() == null ) {
continue ;
}
ret[i] = e.getKey().toString() + "=" + e.getValue().toString();
i++;
}
if (mergeEnv != null ) {
for (Map.Entry e : (Set) mergeEnv.entrySet()) {
if (e.getKey() == null ) {
throw runtime.newTypeError(runtime.getNil(), runtime.getStructClass());
}
if (e.getValue() == null ) {
continue ;
}
ret[i] = e.getKey().toString() + "=" + e.getValue().toString();
i++;
}
}
ary = new String[i];
System.arraycopy(ret, 0 , ary, 0 , i);
return ary;
} finally {
context.setEventHooksEnabled(traceEnabled);
}
}
private static boolean filenameIsPathSearchable (String fname, boolean forExec) {
boolean isSearchable = true ;
if (fname.startsWith("/" ) ||
fname.startsWith("./" ) ||
fname.startsWith("../" ) ||
(forExec && (fname.indexOf("/" ) != -1 ))) {
isSearchable = false ;
}
if (Platform.IS_WINDOWS) {
if (fname.startsWith("\\" ) ||
fname.startsWith(".\\" ) ||
fname.startsWith("..\\" ) ||
((fname.length() > 2 ) && fname.startsWith(":" ,1 )) ||
(forExec && (fname.indexOf("\\" ) != -1 ))) {
isSearchable = false ;
}
}
return isSearchable;
}
private static File tryFile (Ruby runtime, String fdir, String fname) {
File pathFile;
if (fdir == null ) {
pathFile = new File(fname);
} else {
pathFile = new File(fdir, fname);
}
if (!pathFile.isAbsolute()) {
pathFile = new File(runtime.getCurrentDirectory(), pathFile.getPath());
}
log(runtime, "Trying file " + pathFile);
if (pathFile.exists()) {
return pathFile;
} else {
return null ;
}
}
private static boolean withExeSuffix (String fname) {
String lowerCaseFname = fname.toLowerCase();
for (String suffix : WINDOWS_EXE_SUFFIXES) {
if (lowerCaseFname.endsWith(suffix)) {
return true ;
}
}
return false ;
}
private static File isValidFile (Ruby runtime, String fdir, String fname, boolean isExec) {
File validFile = null ;
if (isExec && Platform.IS_WINDOWS) {
if (withExeSuffix(fname)) {
validFile = tryFile(runtime, fdir, fname);
} else {
for (String suffix: WINDOWS_EXE_SUFFIXES) {
validFile = tryFile(runtime, fdir, fname + suffix);
if (validFile != null ) {
break ;
}
}
}
} else {
validFile = tryFile(runtime, fdir, fname);
if (validFile != null ) {
if (validFile.isDirectory()) {
return null ;
}
if (isExec && !runtime.getPosix().stat(validFile.getAbsolutePath()).isExecutable()) {
throw runtime.newErrnoEACCESError(validFile.getAbsolutePath());
}
}
}
return validFile;
}
private static File isValidFile (Ruby runtime, String fname, boolean isExec) {
String fdir = null ;
return isValidFile(runtime, fdir, fname, isExec);
}
private static File findPathFile (Ruby runtime, String fname, String[] path, boolean isExec) {
File pathFile = null ;
boolean doPathSearch = filenameIsPathSearchable(fname, isExec);
if (doPathSearch) {
for (String fdir: path) {
pathFile = isValidFile(runtime, fdir, fname, isExec);
if (pathFile != null ) {
break ;
}
}
} else {
pathFile = isValidFile(runtime, fname, isExec);
}
return pathFile;
}
private static File findPathExecutable (Ruby runtime, String fname) {
RubyHash env = (RubyHash) runtime.getObject().getConstant("ENV" );
IRubyObject pathObject = env.op_aref(runtime.getCurrentContext(), RubyString.newString(runtime, PATH_ENV));
String[] pathNodes = null ;
if (pathObject == null ) {
pathNodes = DEFAULT_PATH;
}
else {
String pathSeparator = System.getProperty("path.separator" );
String path = pathObject.toString();
if (Platform.IS_WINDOWS) {
path = "." + pathSeparator + path;
}
pathNodes = path.split(pathSeparator);
}
return findPathFile(runtime, fname, pathNodes, true );
}
public static int runAndWait (Ruby runtime, IRubyObject[] rawArgs) {
return runAndWait(runtime, rawArgs, runtime.getOutputStream());
}
public static long [] runAndWaitPid(Ruby runtime, IRubyObject[] rawArgs) {
return runAndWaitPid(runtime, rawArgs, runtime.getOutputStream(), true );
}
public static long runWithoutWait (Ruby runtime, IRubyObject[] rawArgs) {
return runWithoutWait(runtime, rawArgs, runtime.getOutputStream());
}
public static int runExternalAndWait (Ruby runtime, IRubyObject[] rawArgs, Map mergeEnv) {
OutputStream output = runtime.getOutputStream();
OutputStream error = runtime.getErrorStream();
InputStream input = runtime.getInputStream();
Process aProcess = null ;
File pwd = new File(runtime.getCurrentDirectory());
LaunchConfig cfg = new LaunchConfig(runtime, rawArgs, true );
try {
try {
if (cfg.shouldRunInShell()) {
log(runtime, "Launching with shell" );
cfg.verifyExecutableForShell();
aProcess = buildProcess(runtime, cfg.getExecArgs(), getCurrentEnv(runtime, mergeEnv), pwd);
} else {
log(runtime, "Launching directly (no shell)" );
cfg.verifyExecutableForDirect();
aProcess = buildProcess(runtime, cfg.getExecArgs(), getCurrentEnv(runtime, mergeEnv), pwd);
}
} catch (SecurityException se) {
throw runtime.newSecurityError(se.getLocalizedMessage());
}
handleStreams(runtime, aProcess, input, output, error);
return aProcess.waitFor();
} catch (IOException e) {
throw runtime.newIOErrorFromException(e);
} catch (InterruptedException e) {
throw runtime.newThreadError("unexpected interrupt" );
}
}
public static long runExternalWithoutWait (Ruby runtime, IRubyObject env, IRubyObject prog, IRubyObject options, IRubyObject args) {
return runExternal(runtime, env, prog, options, args, false );
}
public static long runExternal (Ruby runtime, IRubyObject env, IRubyObject prog, IRubyObject options, IRubyObject args, boolean wait) {
if (env.isNil() || !(env instanceof Map)) {
env = null ;
}
IRubyObject[] rawArgs = args.convertToArray().toJavaArray();
OutputStream output = runtime.getOutputStream();
OutputStream error = runtime.getErrorStream();
InputStream input = runtime.getInputStream();
try {
Process aProcess = null ;
File pwd = new File(runtime.getCurrentDirectory());
LaunchConfig cfg = new LaunchConfig(runtime, rawArgs, true );
try {
if (cfg.shouldRunInShell()) {
log(runtime, "Launching with shell" );
cfg.verifyExecutableForShell();
aProcess = buildProcess(runtime, cfg.getExecArgs(), getCurrentEnv(runtime, (Map)env), pwd);
} else {
log(runtime, "Launching directly (no shell)" );
cfg.verifyExecutableForDirect();
aProcess = buildProcess(runtime, cfg.getExecArgs(), getCurrentEnv(runtime, (Map)env), pwd);
}
} catch (SecurityException se) {
throw runtime.newSecurityError(se.getLocalizedMessage());
}
if (wait) {
handleStreams(runtime, aProcess, input, output, error);
try {
return aProcess.waitFor();
} catch (InterruptedException e) {
throw runtime.newThreadError("unexpected interrupt" );
}
} else {
handleStreamsNonblocking(runtime, aProcess, runtime.getOutputStream(), error);
return getPidFromProcess(aProcess);
}
} catch (IOException e) {
throw runtime.newIOErrorFromException(e);
}
}
public static Process buildProcess (Ruby runtime, String[] args, String[] env, File pwd) throws IOException {
return runtime.getPosix().newProcessMaker(args)
.environment(env)
.directory(pwd)
.start();
}
public static long runExternalWithoutWait (Ruby runtime, IRubyObject[] rawArgs) {
return runWithoutWait(runtime, rawArgs, runtime.getOutputStream());
}
public static int execAndWait (Ruby runtime, IRubyObject[] rawArgs) {
return execAndWait(runtime, rawArgs, Collections.EMPTY_MAP);
}
public static int execAndWait (Ruby runtime, IRubyObject[] rawArgs, Map mergeEnv) {
File pwd = new File(runtime.getCurrentDirectory());
LaunchConfig cfg = new LaunchConfig(runtime, rawArgs, true );
if (cfg.shouldRunInProcess()) {
log(runtime, "ExecAndWait in-process" );
try {
ScriptThreadProcess ipScript = new ScriptThreadProcess(
runtime, cfg.getExecArgs(), getCurrentEnv(runtime, mergeEnv), pwd, false );
ipScript.start();
return ipScript.waitFor();
} catch (IOException e) {
throw runtime.newIOErrorFromException(e);
} catch (InterruptedException e) {
throw runtime.newThreadError("unexpected interrupt" );
}
} else {
return runExternalAndWait(runtime, rawArgs, mergeEnv);
}
}
public static int runAndWait (Ruby runtime, IRubyObject[] rawArgs, OutputStream output) {
return runAndWait(runtime, rawArgs, output, true );
}
public static int runAndWait (Ruby runtime, IRubyObject[] rawArgs, OutputStream output, boolean doExecutableSearch) {
return (int )runAndWaitPid(runtime, rawArgs, output, doExecutableSearch)[0 ];
}
public static long [] runAndWaitPid(Ruby runtime, IRubyObject[] rawArgs, OutputStream output, boolean doExecutableSearch) {
OutputStream error = runtime.getErrorStream();
InputStream input = runtime.getInputStream();
try {
Process aProcess = run(runtime, rawArgs, doExecutableSearch);
handleStreams(runtime, aProcess, input, output, error);
return new long [] {aProcess.waitFor(), getPidFromProcess(aProcess)};
} catch (IOException e) {
throw runtime.newIOErrorFromException(e);
} catch (InterruptedException e) {
throw runtime.newThreadError("unexpected interrupt" );
}
}
private static long runWithoutWait (Ruby runtime, IRubyObject[] rawArgs, OutputStream output) {
OutputStream error = runtime.getErrorStream();
try {
Process aProcess = run(runtime, rawArgs, true );
handleStreamsNonblocking(runtime, aProcess, output, error);
return getPidFromProcess(aProcess);
} catch (IOException e) {
throw runtime.newIOErrorFromException(e);
}
}
private static long runExternalWithoutWait (Ruby runtime, IRubyObject[] rawArgs, OutputStream output) {
OutputStream error = runtime.getErrorStream();
try {
Process aProcess = run(runtime, rawArgs, true , true );
handleStreamsNonblocking(runtime, aProcess, output, error);
return getPidFromProcess(aProcess);
} catch (IOException e) {
throw runtime.newIOErrorFromException(e);
}
}
public static long getPidFromProcess (Process process) {
if (process instanceof ScriptThreadProcess) {
return process.hashCode();
} else if (process instanceof POpenProcess) {
return reflectPidFromProcess(((POpenProcess)process).getChild());
} else {
return reflectPidFromProcess(process);
}
}
private static final Class UNIXProcess;
private static final Field UNIXProcess_pid;
private static final Class ProcessImpl;
private static final Field ProcessImpl_handle;
private interface PidGetter { public long getPid (Process process) ; }
private static final PidGetter PID_GETTER;
static {
PidGetter pg = new PidGetter() {
public long getPid (Process process) {
return process.hashCode();
}
};
Class up = null ;
Field pid = null ;
try {
up = Class.forName("java.lang.UNIXProcess" );
pid = up.getDeclaredField("pid" );
pid.setAccessible(true );
} catch (Exception e) {
}
UNIXProcess = up;
UNIXProcess_pid = pid;
Class pi = null ;
Field handle = null ;
try {
pi = Class.forName("java.lang.ProcessImpl" );
handle = pi.getDeclaredField("handle" );
handle.setAccessible(true );
} catch (Exception e) {
}
ProcessImpl = pi;
ProcessImpl_handle = handle;
if (UNIXProcess_pid != null ) {
if (ProcessImpl_handle != null ) {
pg = new PidGetter() {
public long getPid (Process process) {
try {
if (UNIXProcess.isInstance(process)) {
return (Integer)UNIXProcess_pid.get(process);
} else if (ProcessImpl.isInstance(process)) {
Long hproc = (Long) ProcessImpl_handle.get(process);
return WindowsFFI.getKernel32().GetProcessId(hproc);
}
} catch (Exception e) {
}
return process.hashCode();
}
};
} else {
pg = new PidGetter() {
public long getPid (Process process) {
try {
if (UNIXProcess.isInstance(process)) {
return (Integer)UNIXProcess_pid.get(process);
}
} catch (Exception e) {
}
return process.hashCode();
}
};
}
} else if (ProcessImpl_handle != null ) {
pg = new PidGetter() {
public long getPid (Process process) {
try {
if (ProcessImpl.isInstance(process)) {
Long hproc = (Long) ProcessImpl_handle.get(process);
return WindowsFFI.getKernel32().GetProcessId(hproc);
}
} catch (Exception e) {
}
return process.hashCode();
}
};
} else {
pg = new PidGetter() {
public long getPid (Process process) {
return process.hashCode();
}
};
}
PID_GETTER = pg;
}
public static long reflectPidFromProcess (Process process) {
return PID_GETTER.getPid(process);
}
public static Process run (Ruby runtime, IRubyObject string) throws IOException {
return run(runtime, new IRubyObject[]{string}, false );
}
public static POpenProcess popen (Ruby runtime, IRubyObject string, ModeFlags modes) throws IOException {
return new POpenProcess(popenShared(runtime, new IRubyObject[] {string}, null , true ), runtime, modes);
}
public static POpenProcess popen (Ruby runtime, IRubyObject[] strings, Map env, ModeFlags modes) throws IOException {
return new POpenProcess(popenShared(runtime, strings, env), runtime, modes);
}
public static POpenProcess popen (Ruby runtime, IRubyObject string, Map env, ModeFlags modes) throws IOException {
return new POpenProcess(popenShared(runtime, new IRubyObject[] {string}, env, true ), runtime, modes);
}
@Deprecated
public static POpenProcess popen (Ruby runtime, IRubyObject string, IOOptions modes) throws IOException {
return new POpenProcess(popenShared(runtime, new IRubyObject[] {string}, null , true ), runtime, modes);
}
@Deprecated
public static POpenProcess popen (Ruby runtime, IRubyObject[] strings, Map env, IOOptions modes) throws IOException {
return new POpenProcess(popenShared(runtime, strings, env), runtime, modes);
}
public static POpenProcess popen3 (Ruby runtime, IRubyObject[] strings) throws IOException {
return new POpenProcess(popenShared(runtime, strings));
}
public static POpenProcess popen3 (Ruby runtime, IRubyObject[] strings, boolean addShell) throws IOException {
return new POpenProcess(popenShared(runtime, strings, null , addShell));
}
private static Process popenShared (Ruby runtime, IRubyObject[] strings) throws IOException {
return popenShared(runtime, strings, null );
}
private static Process popenShared (Ruby runtime, IRubyObject[] strings, Map env) throws IOException {
return popenShared(runtime, strings, env, false );
}
private static Process popenShared (Ruby runtime, IRubyObject[] strings, Map env, boolean addShell) throws IOException {
String shell = getShell(runtime);
Process childProcess = null ;
File pwd = new File(runtime.getCurrentDirectory());
try {
IRubyObject envHash = null ;
if (env == null && strings.length > 0 && !(envHash = TypeConverter.checkHashType(runtime, strings[0 ])).isNil()) {
strings = Arrays.copyOfRange(strings, 1 , strings.length);
env = (Map)envHash;
}
if (strings.length > 1 && !(envHash = TypeConverter.checkHashType(runtime, strings[strings.length - 1 ])).isNil()) {
if (!((RubyHash)envHash).isEmpty()) {
runtime.getWarnings().warn("popen3 does not support spawn options in JRuby 1.7" );
}
strings = Arrays.copyOfRange(strings, 0 , strings.length - 1 );
}
String[] args = parseCommandLine(runtime.getCurrentContext(), runtime, strings);
LaunchConfig lc = new LaunchConfig(runtime, strings, false );
boolean useShell = Platform.IS_WINDOWS ? lc.shouldRunInShell() : false ;
if (addShell) for (String arg : args) useShell |= shouldUseShell(arg);
if (strings.length == 1 ) {
if (useShell) {
String[] argArray = new String[3 ];
argArray[0 ] = shell;
argArray[1 ] = shell.endsWith("sh" ) ? "-c" : "/c" ;
argArray[2 ] = strings[0 ].asJavaString();
childProcess = buildProcess(runtime, argArray, getCurrentEnv(runtime, env), pwd);
} else {
childProcess = buildProcess(runtime, args, getCurrentEnv(runtime, env), pwd);
}
} else {
if (useShell) {
String[] argArray = new String[args.length + 2 ];
argArray[0 ] = shell;
argArray[1 ] = shell.endsWith("sh" ) ? "-c" : "/c" ;
System.arraycopy(args, 0 , argArray, 2 , args.length);
childProcess = buildProcess(runtime, argArray, getCurrentEnv(runtime, env), pwd);
} else {
childProcess = buildProcess(runtime, args, getCurrentEnv(runtime, env), pwd);
}
}
} catch (SecurityException se) {
throw runtime.newSecurityError(se.getLocalizedMessage());
}
return childProcess;
}
public static OutputStream unwrapBufferedStream (OutputStream filteredStream) {
if (RubyInstanceConfig.NO_UNWRAP_PROCESS_STREAMS) return filteredStream;
while (filteredStream instanceof FilterOutputStream) {
try {
filteredStream = (OutputStream)
FieldAccess.getProtectedFieldValue(FilterOutputStream.class,
"out" , filteredStream);
} catch (Exception e) {
break ;
}
}
return filteredStream;
}
public static InputStream unwrapBufferedStream (InputStream filteredStream) {
if (RubyInstanceConfig.NO_UNWRAP_PROCESS_STREAMS) return filteredStream;
if (filteredStream.getClass().getName().indexOf("ProcessPipeInputStream" ) != 1 ) {
return filteredStream;
}
while (filteredStream instanceof FilterInputStream) {
try {
filteredStream = (InputStream)
FieldAccess.getProtectedFieldValue(FilterInputStream.class,
"in" , filteredStream);
} catch (Exception e) {
break ;
}
}
return filteredStream;
}
public static class POpenProcess extends Process {
private final Process child;
private final boolean waitForChild;
private InputStream realInput;
private OutputStream realOutput;
private InputStream realInerr;
private InputStream input;
private OutputStream output;
private InputStream inerr;
private FileChannel inputChannel;
private FileChannel outputChannel;
private FileChannel inerrChannel;
private Pumper inputPumper;
private Pumper inerrPumper;
@Deprecated
public POpenProcess (Process child, Ruby runtime, IOOptions modes) {
this (child, runtime, modes.getModeFlags());
}
public POpenProcess (Process child, Ruby runtime, ModeFlags modes) {
this .child = child;
if (modes.isWritable()) {
this .waitForChild = true ;
prepareOutput(child);
} else {
this .waitForChild = false ;
try {child.getOutputStream().close();} catch (IOException ioe) {}
}
if (modes.isReadable()) {
prepareInput(child);
} else {
pumpInput(child, runtime);
}
pumpInerr(child, runtime);
}
public POpenProcess (Process child) {
this .child = child;
this .waitForChild = false ;
prepareOutput(child);
prepareInput(child);
prepareInerr(child);
}
@Override
public OutputStream getOutputStream () {
return output;
}
@Override
public InputStream getInputStream () {
return input;
}
@Override
public InputStream getErrorStream () {
return inerr;
}
public FileChannel getInput () {
return inputChannel;
}
public FileChannel getOutput () {
return outputChannel;
}
public FileChannel getError () {
return inerrChannel;
}
public boolean hasOutput () {
return output != null || outputChannel != null ;
}
public Process getChild () {
return child;
}
@Override
public int waitFor () throws InterruptedException {
return child.waitFor();
}
@Override
public int exitValue () {
return child.exitValue();
}
@Override
public void destroy () {
try {
try {if (input != null ) input.close();} catch (Exception e) {}
try {if (inerr != null ) inerr.close();} catch (Exception e) {}
try {if (output != null ) output.close();} catch (Exception e) {}
try {if (inputChannel != null ) inputChannel.close();} catch (Exception e) {}
try {if (inerrChannel != null ) inerrChannel.close();} catch (Exception e) {}
try {if (outputChannel != null ) outputChannel.close();} catch (Exception e) {}
synchronized (this ) {
if (inputPumper != null ) synchronized (inputPumper) {inputPumper.quit();}
if (inerrPumper != null ) synchronized (inerrPumper) {inerrPumper.quit();}
if (waitForChild) {
waitFor();
} else {
RubyIO.obliterateProcess(child);
}
}
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
private void prepareInput (Process child) {
realInput = child.getInputStream();
input = unwrapBufferedStream(realInput);
if (input instanceof FileInputStream) {
} else {
inputChannel = null ;
}
inputPumper = null ;
}
private void prepareInerr (Process child) {
realInerr = child.getErrorStream();
inerr = unwrapBufferedStream(realInerr);
if (inerr instanceof FileInputStream) {
inerrChannel = ((FileInputStream) inerr).getChannel();
} else {
inerrChannel = null ;
}
inerrPumper = null ;
}
private void prepareOutput (Process child) {
realOutput = child.getOutputStream();
output = unwrapBufferedStream(realOutput);
if (output instanceof FileOutputStream) {
outputChannel = ((FileOutputStream) output).getChannel();
} else {
outputChannel = null ;
}
}
private void pumpInput (Process child, Ruby runtime) {
InputStream childIn = unwrapBufferedStream(child.getInputStream());
FileChannel childInChannel = null ;
if (childIn instanceof FileInputStream) {
childInChannel = ((FileInputStream) childIn).getChannel();
}
OutputStream parentOut = unwrapBufferedStream(runtime.getOut());
FileChannel parentOutChannel = null ;
if (parentOut instanceof FileOutputStream) {
parentOutChannel = ((FileOutputStream) parentOut).getChannel();
}
if (childInChannel != null && parentOutChannel != null ) {
inputPumper = new ChannelPumper(runtime, childInChannel, parentOutChannel, Pumper.Slave.IN, this );
} else {
inputPumper = new StreamPumper(runtime, childIn, parentOut, false , Pumper.Slave.IN, this );
}
inputPumper.start();
input = null ;
inputChannel = null ;
}
private void pumpInerr (Process child, Ruby runtime) {
InputStream childIn = unwrapBufferedStream(child.getErrorStream());
FileChannel childInChannel = null ;
if (childIn instanceof FileInputStream) {
childInChannel = ((FileInputStream) childIn).getChannel();
}
OutputStream parentOut = unwrapBufferedStream(runtime.getOut());
FileChannel parentOutChannel = null ;
if (parentOut instanceof FileOutputStream) {
parentOutChannel = ((FileOutputStream) parentOut).getChannel();
}
if (childInChannel != null && parentOutChannel != null ) {
inerrPumper = new ChannelPumper(runtime, childInChannel, parentOutChannel, Pumper.Slave.IN, this );
} else {
inerrPumper = new StreamPumper(runtime, childIn, parentOut, false , Pumper.Slave.IN, this );
}
inerrPumper.start();
inerr = null ;
inerrChannel = null ;
}
}
public static class LaunchConfig {
public LaunchConfig (Ruby runtime, IRubyObject[] rawArgs, boolean doExecutableSearch) {
this .runtime = runtime;
this .rawArgs = rawArgs;
this .doExecutableSearch = doExecutableSearch;
shell = getShell(runtime);
args = parseCommandLine(runtime.getCurrentContext(), runtime, rawArgs);
}
public boolean shouldRunInProcess () {
if (!runtime.getInstanceConfig().isRunRubyInProcess()
|| RubyInstanceConfig.hasLoadedNativeExtensions()) {
return false ;
}
for (int i = 0 ; i < args.length; i++) {
String c = args[i];
if (c.trim().length() == 0 ) continue ;
char [] firstLast = new char [] {c.charAt(0 ), c.charAt(c.length()-1 )};
for (int j = 0 ; j < firstLast.length; j++) {
switch (firstLast[j]) {
case '<' : case '>' : case '|' : case ';' : case '(' : case ')' :
case '~' : case '&' : case '$' : case '"' : case '`' : case '\n' :
case '\\' : case '\'' :
return false ;
case '2' :
if (c.length() > 1 && c.charAt(1 ) == '>' ) return false ;
}
}
}
String command = args[0 ];
if (Platform.IS_WINDOWS) command = command.toLowerCase();
String[] slashDelimitedTokens = command.split("[/\\\\]" );
String finalToken = slashDelimitedTokens[slashDelimitedTokens.length - 1 ];
boolean inProc = (finalToken.endsWith("ruby" )
|| (Platform.IS_WINDOWS && finalToken.endsWith("ruby.exe" ))
|| finalToken.endsWith(".rb" )
|| finalToken.endsWith("irb" ));
if (!inProc) return false ;
int startIndex = command.endsWith(".rb" ) ? 0 : 1 ;
if (command.trim().endsWith("irb" )) {
startIndex = 0 ;
args[0 ] = runtime.getJRubyHome() + File.separator + "bin" + File.separator + "jirb" ;
}
execArgs = new String[args.length - startIndex];
System.arraycopy(args, startIndex, execArgs, 0 , execArgs.length);
return true ;
}
public boolean shouldRunInShell () {
if (rawArgs.length != 1 ) {
return false ;
}
if (!Platform.IS_WINDOWS) return true ;
if (shell == null ) return false ;
for (String arg : args) {
if (!shouldVerifyPathExecutable(arg.trim())) {
return true ;
}
}
executable = args[0 ].trim();
executableFile = findPathExecutable(runtime, executable);
if (executableFile != null ) {
log(runtime, "Got it: " + executableFile);
return false ;
} else {
log(runtime, "Didn't find executable: " + executable);
}
if (isCmdBuiltin(executable)) {
cmdBuiltin = true ;
return true ;
}
return false ;
}
public void verifyExecutableForShell () {
String cmdline = rawArgs[0 ].toString().trim();
if (doExecutableSearch && shouldVerifyPathExecutable(cmdline) && !cmdBuiltin) {
verifyExecutable();
}
execArgs = new String[3 ];
execArgs[0 ] = shell;
execArgs[1 ] = shell.endsWith("sh" ) ? "-c" : "/c" ;
if (Platform.IS_WINDOWS) {
execArgs[2 ] = "\"" + cmdline + "\"" ;
} else {
execArgs[2 ] = cmdline;
}
}
public void verifyExecutableForDirect () {
if (isCmdBuiltin(args[0 ].trim())) {
execArgs = new String[args.length + 2 ];
execArgs[0 ] = shell;
execArgs[1 ] = "/c" ;
execArgs[2 ] = args[0 ].trim();
System.arraycopy(args, 1 , execArgs, 3 , args.length - 1 );
} else {
verifyExecutable();
execArgs = args;
try {
execArgs[0 ] = executableFile.getCanonicalPath();
} catch (IOException ioe) {
}
}
}
private void verifyExecutable () {
if (executableFile == null ) {
if (executable == null ) {
executable = args[0 ].trim();
}
executableFile = findPathExecutable(runtime, executable);
}
if (executableFile == null ) {
throw runtime.newErrnoENOENTError(executable);
}
}
public String[] getExecArgs() {
return execArgs;
}
private static boolean isBatch (File f) {
String path = f.getPath();
return (path.endsWith(".bat" ) || path.endsWith(".cmd" ));
}
private boolean isCmdBuiltin (String cmd) {
if (!shell.endsWith("sh" )) {
int idx = Arrays.binarySearch(WINDOWS_INTERNAL_CMDS, cmd.toLowerCase());
if (idx >= 0 ) {
log(runtime, "Found Windows shell's built-in command: " + cmd);
return true ;
}
}
return false ;
}
private static boolean hasRedirection (String cmdline) {
if (Platform.IS_WINDOWS) {
char quote = '\0' ;
for (int idx = 0 ; idx < cmdline.length();) {
char ptr = cmdline.charAt(idx);
switch (ptr) {
case '\'' :
case '\"' :
if (quote == '\0' ) {
quote = ptr;
} else if (quote == ptr) {
quote = '\0' ;
}
idx++;
break ;
case '>' :
case '<' :
case '|' :
case '\n' :
if (quote == '\0' ) {
return true ;
}
idx++;
break ;
case '%' :
Matcher envVarMatcher = WIN_ENVVAR_PATTERN.matcher(cmdline.substring(idx));
if (envVarMatcher.find()) {
return true ;
} else {
idx++;
}
break ;
case '\\' :
idx++;
default :
idx++;
break ;
}
}
return false ;
} else {
Matcher metaMatcher = SHELL_METACHARACTER_PATTERN.matcher(cmdline);
return metaMatcher.find();
}
}
private static boolean shouldVerifyPathExecutable (String cmdline) {
boolean verifyPathExecutable = true ;
if (hasRedirection(cmdline)) {
return false ;
}
return verifyPathExecutable;
}
private Ruby runtime;
private boolean doExecutableSearch;
private IRubyObject[] rawArgs;
private String shell;
private String[] args;
private String[] execArgs;
private boolean cmdBuiltin = false ;
private String executable;
private File executableFile;
}
public static Process run (Ruby runtime, IRubyObject[] rawArgs, boolean doExecutableSearch) throws IOException {
return run(runtime, rawArgs, doExecutableSearch, false );
}
private static boolean hasGlobCharacter (String word) {
return word.contains("*" ) || word.contains("?" ) || word.contains("[" ) || word.contains("{" );
}
private static String[] expandGlobs(Ruby runtime, String[] originalArgs) {
List expandedList = new ArrayList(originalArgs.length);
for (int i = 0 ; i < originalArgs.length; i++) {
if (hasGlobCharacter(originalArgs[i])) {
List globs = Dir.push_glob(runtime.getPosix(), runtime.getCurrentDirectory(),
new ByteList(originalArgs[i].getBytes()), 0 );
for (ByteList glob: globs) {
expandedList.add(glob.toString());
}
} else {
expandedList.add(originalArgs[i]);
}
}
String[] args = new String[expandedList.size()];
expandedList.toArray(args);
return args;
}
public static Process run (Ruby runtime, IRubyObject[] rawArgs, boolean doExecutableSearch, boolean forceExternalProcess) throws IOException {
Process aProcess;
File pwd = new File(runtime.getCurrentDirectory());
LaunchConfig cfg = new LaunchConfig(runtime, rawArgs, doExecutableSearch);
try {
if (!forceExternalProcess && cfg.shouldRunInProcess()) {
log(runtime, "Launching in-process" );
ScriptThreadProcess ipScript = new ScriptThreadProcess(runtime,
expandGlobs(runtime, cfg.getExecArgs()), getCurrentEnv(runtime), pwd);
ipScript.start();
return ipScript;
} else {
if (cfg.shouldRunInShell()) {
log(runtime, "Launching with shell" );
cfg.verifyExecutableForShell();
} else {
log(runtime, "Launching directly (no shell)" );
cfg.verifyExecutableForDirect();
}
aProcess = buildProcess(runtime, cfg.getExecArgs(), getCurrentEnv(runtime), pwd);
}
} catch (SecurityException se) {
throw runtime.newSecurityError(se.getLocalizedMessage());
}
return aProcess;
}
private interface Pumper extends Runnable {
public enum Slave { IN, OUT };
public void start () ;
public void quit () ;
}
private static class StreamPumper extends Thread implements Pumper {
private final InputStream in;
private final OutputStream out;
private final boolean onlyIfAvailable;
private final Object waitLock = new Object();
private final Object sync;
private final Slave slave;
private volatile boolean quit;
private final Ruby runtime;
StreamPumper(Ruby runtime, InputStream in, OutputStream out, boolean avail, Slave slave, Object sync) {
this .in = unwrapBufferedStream(in);
this .out = unwrapBufferedStream(out);
this .onlyIfAvailable = avail;
this .slave = slave;
this .sync = sync;
this .runtime = runtime;
setDaemon(true );
}
@Override
public void run () {
runtime.getCurrentContext().setEventHooksEnabled(false );
byte [] buf = new byte [1024 ];
int numRead;
boolean hasReadSomething = false ;
try {
while (!quit) {
if (onlyIfAvailable && !hasReadSomething) {
if (in.available() == 0 ) {
synchronized (waitLock) {
waitLock.wait(10 );
}
continue ;
} else {
hasReadSomething = true ;
}
}
if ((numRead = in.read(buf)) == -1 ) {
break ;
}
out.write(buf, 0 , numRead);
}
} catch (Exception e) {
} finally {
if (onlyIfAvailable) {
synchronized (sync) {
if (slave == Slave.OUT) {
try { out.close(); } catch (IOException ioe) {}
}
}
}
}
}
public void quit () {
this .quit = true ;
synchronized (waitLock) {
waitLock.notify();
}
stop();
}
}
private static class ChannelPumper extends Thread implements Pumper {
private final FileChannel inChannel;
private final FileChannel outChannel;
private final Slave slave;
private final Object sync;
private volatile boolean quit;
private final Ruby runtime;
ChannelPumper(Ruby runtime, FileChannel inChannel, FileChannel outChannel, Slave slave, Object sync) {
if (DEBUG) out.println("using channel pumper" );
this .inChannel = inChannel;
this .outChannel = outChannel;
this .slave = slave;
this .sync = sync;
this .runtime = runtime;
setDaemon(true );
}
@Override
public void run () {
runtime.getCurrentContext().setEventHooksEnabled(false );
ByteBuffer buf = ByteBuffer.allocateDirect(1024 );
buf.clear();
try {
while (!quit && inChannel.isOpen() && outChannel.isOpen()) {
int read = inChannel.read(buf);
if (read == -1 ) break ;
buf.flip();
outChannel.write(buf);
buf.clear();
}
} catch (Exception e) {
} finally {
synchronized (sync) {
switch (slave) {
case OUT:
try { outChannel.close(); } catch (IOException ioe) {}
break ;
case IN:
try { inChannel.close(); } catch (IOException ioe) {}
}
}
}
}
public void quit () {
interrupt();
this .quit = true ;
stop();
}
}
private static void handleStreams (Ruby runtime, Process p, InputStream in, OutputStream out, OutputStream err) throws IOException {
InputStream pOut = p.getInputStream();
InputStream pErr = p.getErrorStream();
OutputStream pIn = p.getOutputStream();
StreamPumper t1 = new StreamPumper(runtime, pOut, out, false , Pumper.Slave.IN, p);
StreamPumper t2 = new StreamPumper(runtime, pErr, err, false , Pumper.Slave.IN, p);
StreamPumper t3 = new StreamPumper(runtime, in, pIn, true , Pumper.Slave.OUT, p);
t1.start();
t2.start();
t3.start();
try { t1.join(); } catch (InterruptedException ie) {}
try { t2.join(); } catch (InterruptedException ie) {}
t3.quit();
try { err.flush(); } catch (IOException io) {}
try { out.flush(); } catch (IOException io) {}
try { pIn.close(); } catch (IOException io) {}
try { pOut.close(); } catch (IOException io) {}
try { pErr.close(); } catch (IOException io) {}
try { t3.interrupt(); } catch (SecurityException se) {}
t1.stop();
t2.stop();
t3.stop();
try { t1.join(); } catch (InterruptedException ie) {}
try { t2.join(); } catch (InterruptedException ie) {}
try { t3.join(); } catch (InterruptedException ie) {}
}
private static void handleStreamsNonblocking (Ruby runtime, Process p, OutputStream out, OutputStream err) throws IOException {
InputStream pOut = p.getInputStream();
InputStream pErr = p.getErrorStream();
StreamPumper t1 = new StreamPumper(runtime, pOut, out, false , Pumper.Slave.IN, p);
StreamPumper t2 = new StreamPumper(runtime, pErr, err, false , Pumper.Slave.IN, p);
t1.start();
t2.start();
}
private static String[] parseCommandLine(ThreadContext context, Ruby runtime, IRubyObject[] rawArgs) {
String[] args;
if (rawArgs.length == 1 ) {
if (hasLeadingArgvArray(rawArgs)) {
args = new String[] { getPathEntry((RubyArray) rawArgs[0 ]) };
} else {
synchronized (runtime.getLoadService()) {
runtime.getLoadService().require("jruby/path_helper" );
}
RubyModule pathHelper = runtime.getClassFromPath("JRuby::PathHelper" );
RubyArray parts = (RubyArray) Helpers.invoke(
context, pathHelper, "smart_split_command" , rawArgs);
args = new String[parts.getLength()];
for (int i = 0 ; i < parts.getLength(); i++) {
args[i] = parts.entry(i).toString();
}
}
} else {
args = new String[rawArgs.length];
int start = 0 ;
if (hasLeadingArgvArray(rawArgs)) {
start = 1 ;
args[0 ] = getPathEntry((RubyArray) rawArgs[0 ]);
}
for (int i = start; i < rawArgs.length; i++) {
args[i] = rawArgs[i].toString();
}
}
return args;
}
private static boolean hasLeadingArgvArray (IRubyObject[] rawArgs) {
return (rawArgs.length >= 1
&& (rawArgs[0 ] instanceof RubyArray)
&& (((RubyArray) rawArgs[0 ]).getLength() == 2 ));
}
private static String getPathEntry (RubyArray initArray) {
return initArray.entry(0 ).toString();
}
private static String getShell (Ruby runtime) {
return RbConfigLibrary.jrubyShell();
}
private static boolean shouldUseShell (String command) {
boolean useShell = false ;
for (char c : command.toCharArray()) {
if (c != ' ' && !Character.isLetter(c) && "*?{}[]<>()~&|\\$;'`\"\n" .indexOf(c) != -1 ) {
useShell = true ;
}
}
if (Platform.IS_WINDOWS && command.length() >= 1 && command.charAt(0 ) == '@' ) {
useShell = true ;
}
return useShell;
}
static void log (Ruby runtime, String msg) {
if (RubyInstanceConfig.DEBUG_LAUNCHING) {
runtime.getErr().println("ShellLauncher: " + msg);
}
}
}