deep-c-rsc/JCGO/goclsp/vm/java/lang/VMProcess.java
2021-07-16 17:12:20 -05:00

672 lines
17 KiB
Java

/*
* @(#) $(JCGO)/goclsp/vm/java/lang/VMProcess.java --
* VM specific implementation of Java "Process".
**
* Project: JCGO (http://www.ivmaisoft.com/jcgo/)
* Copyright (C) 2001-2009 Ivan Maidanski <ivmai@ivmaisoft.com>
* All rights reserved.
**
* Class specification origin: GNU Classpath v0.93 vm/reference
*/
/*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
**
* This software is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License (GPL) for more details.
**
* Linking this library statically or dynamically with other modules is
* making a combined work based on this library. Thus, the terms and
* conditions of the GNU General Public License cover the whole
* combination.
**
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under
* terms of your choice, provided that you also meet, for each linked
* independent module, the terms and conditions of the license of that
* module. An independent module is a module which is not derived from
* or based on this library. If you modify this library, you may extend
* this exception to your version of the library, but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package java.lang;
import gnu.classpath.SystemProperties;
import gnu.java.nio.FileChannelImpl;
import gnu.java.nio.VMAccessorGnuJavaNio;
import gnu.java.nio.VMChannel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.VMAccessorJavaIo;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
final class VMProcess extends Process /* hard-coded class name */
{
private static final int STATE_INITIAL = 0;
private static final int STATE_RUNNING = 1;
private static final int STATE_TERMINATED = 2;
private static int threadNum;
private static final String[] PROPAGATE_ENVS_LIST =
{
"LD_LIBRARY_PATH", "TMPDIR", "HOME", "LOGNAME", "LC_CTYPE", "TZ",
"COMSPEC", "TMP", "TEMP"
};
private static final boolean IS_PROG_SEARCH_NEEDED =
isProgSearchNeeded0() != 0;
private static final int IS_CMD_SPACE_DELIMITED = isCmdSpaceDelimited0();
private static final String VALID_PROG_EXTS_LIST = getValidProgExtsList();
private static final boolean preventBlocking = VMRuntime.preventIOBlocking();
volatile int state;
long pid;
OutputStream stdinStream;
InputStream stdoutStream;
InputStream stderrStream;
int exitValue;
private VMProcess() {}
private VMProcess(final String[] cmd, final String[] env, final File dir,
final boolean redirect)
throws IOException
{
final Throwable[] throwableArr = new Throwable[1];
ThreadGroup tg = Thread.currentThread().getThreadGroup();
try
{
for (ThreadGroup tgn = tg; tgn != null; tgn = tg.getParent())
tg = tgn;
}
catch (SecurityException e) {}
Thread processThread =
new Thread(tg, "Process reaper-".concat(String.valueOf(nextThreadNum())))
{
public void run()
{
synchronized (VMProcess.this)
{
try
{
nativeSpawn(cmd, env, dir, redirect);
state = STATE_RUNNING;
}
catch (ThreadDeath e)
{
state = STATE_TERMINATED;
throw e;
}
catch (Throwable e)
{
state = STATE_TERMINATED;
throwableArr[0] = e;
}
VMProcess.this.notifyAll();
}
waitForProcess();
}
};
processThread.setDaemon(true);
try
{
processThread.start();
}
catch (OutOfMemoryError e)
{
processThread.run();
}
synchronized (this)
{
while (state == STATE_INITIAL)
{
try
{
wait();
}
catch (InterruptedException e) {}
}
}
Throwable throwable = throwableArr[0];
if (throwable != null)
{
throwable.fillInStackTrace();
if (throwable instanceof IOException)
throw (IOException) throwable;
if (throwable instanceof RuntimeException)
throw (RuntimeException) throwable;
if (throwable instanceof Error)
throw (Error) throwable;
throw (Error) (new InternalError("VMProcess")).initCause(throwable);
}
}
void setProcessInfo(OutputStream stdin, InputStream stdout,
InputStream stderr, long pid)
{
stdinStream = stdin != null ? stdin :
new OutputStream()
{
public void write(int b)
throws IOException
{
throw new IOException("process input stream is not redirected");
}
};
InputStream nulIn = stdout != null && stderr != null ? null :
new InputStream()
{
public int read()
throws IOException
{
return -1;
}
};
stdoutStream = stdout != null ? stdout : nulIn;
stderrStream = stderr != null ? stderr : nulIn;
this.pid = pid;
}
static Process exec(String[] cmd, String[] env, File dir)
throws IOException
{
return new VMProcess(cmd, env, dir, false);
}
static Process exec(List cmdList, Map envMap, File dir, boolean redirect)
throws IOException
{
String[] cmd = new String[cmdList.size()];
Iterator iter = cmdList.iterator();
for (int i = 0; iter.hasNext(); i++)
cmd[i] = (String) iter.next();
String[] env = new String[envMap.size()];
iter = envMap.entrySet().iterator();
for (int i = 0; iter.hasNext(); i++)
{
Map.Entry entry = (Map.Entry) iter.next();
StringBuilder sb = new StringBuilder();
sb.append((String) entry.getKey());
sb.append('=');
sb.append((String) entry.getValue());
env[i] = sb.toString();
}
return new VMProcess(cmd, env, dir, redirect);
}
public OutputStream getOutputStream()
{
return stdinStream;
}
public InputStream getInputStream()
{
return stdoutStream;
}
public InputStream getErrorStream()
{
return stderrStream;
}
public synchronized int waitFor()
throws InterruptedException
{
while (state != STATE_TERMINATED)
wait();
return exitValue;
}
public synchronized int exitValue()
{
if (state != STATE_TERMINATED)
throw new IllegalThreadStateException();
return exitValue;
}
public synchronized void destroy()
{
if (state != STATE_TERMINATED)
{
int res = nativeKill(pid);
if (res > 0)
{
if (exitValue == 0)
exitValue = res;
state = STATE_TERMINATED;
notifyAll();
}
else
{
do
{
try
{
wait();
}
catch (InterruptedException e) {}
} while (state != STATE_TERMINATED);
}
}
}
final void nativeSpawn(String[] cmd, String[] env, File dir,
boolean redirect)
throws IOException
{ /* used by VM classes only */
if (cmd == null)
throw new NullPointerException();
int res = cmd.length;
if (res == 0)
throw new IndexOutOfBoundsException();
do
{
if (cmd[--res] == null)
throw new NullPointerException();
} while (res > 0);
String envZBlock = "";
if (env != null)
{
env = addMissingEnvVar(env, "PATH", ".");
for (int i = 0; i < PROPAGATE_ENVS_LIST.length; i++)
env = addMissingEnvVar(env, PROPAGATE_ENVS_LIST[i], null);
envZBlock = convertEnv(env);
}
String dirpath = ".";
boolean changeDir = false;
if (dir != null)
{
dirpath = VMAccessorJavaIo.normVolumeColonVMFile(dir.getPath());
if (!dirpath.equals("."))
{
VMAccessorGnuJavaNio.checkIOResCodeVMChannel(
checkPermissions0(dirpath, 1));
changeDir = true;
}
}
String progName;
String cmdZBlock;
if (IS_CMD_SPACE_DELIMITED < 0)
{
progName = convertProgName(cmd[0], true);
cmdZBlock = convertCmd(null, cmd, true);
}
else
{
cmdZBlock = convertCmd(convertProgName(cmd[0], changeDir), cmd,
IS_CMD_SPACE_DELIMITED != 0);
progName = "";
}
int bufLen = getSpawnWorkBufSize0(cmdZBlock, envZBlock, cmd.length,
env != null ? env.length : 0);
byte[] workBuf = new byte[bufLen >= 0 ? bufLen : -1 >>> 1];
VMChannel inCh = new VMChannel();
VMChannel outCh = new VMChannel();
VMChannel errCh = redirect ? null : new VMChannel();
int[] fdsArr = { -1, -1, -1 };
long[] pidArr = new long[1];
boolean retrying = false;
do
{
res = nativeSpawn0(progName, cmdZBlock, envZBlock, dirpath, fdsArr, pidArr,
workBuf, bufLen, redirect ? 1 : 0);
if (retrying || !VMAccessorGnuJavaNio.isIORetryNeededOnceVMChannel(res))
break;
retrying = true;
} while (true);
VMAccessorGnuJavaNio.checkIOResCodeVMChannel(res);
if (fdsArr[2] == -1)
errCh = null;
try
{
if (fdsArr[0] != -1)
VMAccessorGnuJavaNio.setNativeFileFDVMChannel(inCh, fdsArr[0],
FileChannelImpl.WRITE);
}
finally
{
try
{
if (fdsArr[1] != -1)
VMAccessorGnuJavaNio.setNativeFileFDVMChannel(outCh, fdsArr[1],
FileChannelImpl.READ);
}
finally
{
if (errCh != null)
VMAccessorGnuJavaNio.setNativeFileFDVMChannel(errCh, fdsArr[2],
FileChannelImpl.READ);
}
}
setProcessInfo(fdsArr[0] != -1 ? createOutputStream(inCh) : null,
fdsArr[1] != -1 ? createInputStream(outCh) : null,
errCh != null ? createInputStream(errCh) : null, pidArr[0]);
}
final void waitForProcess()
{ /* used by VM classes only */
int nohang = 0;
if (preventBlocking)
{
Thread.yield();
nohang = 1;
}
int res;
do
{
res = nativeWaitFor0(pid, nohang);
if (state == STATE_TERMINATED)
return;
if (res != -1)
break;
try
{
Thread.sleep(1L);
}
catch (InterruptedException e) {}
} while (true);
exitValue = res;
synchronized (this)
{
if (state != STATE_TERMINATED)
{
state = STATE_TERMINATED;
notifyAll();
}
}
}
static final void pipe(VMChannel chIn, VMChannel chOut)
throws IOException
{ /* used by VM classes only */
int[] fdsArr = { -1, -1 };
int res;
boolean retrying = false;
do
{
res = pipe0(fdsArr);
if (retrying || !VMAccessorGnuJavaNio.isIORetryNeededOnceVMChannel(res))
break;
retrying = true;
} while (true);
VMAccessorGnuJavaNio.checkIOResCodeVMChannel(res);
try
{
VMAccessorGnuJavaNio.setNativeFileFDVMChannel(chIn, fdsArr[0],
FileChannelImpl.READ);
}
finally
{
VMAccessorGnuJavaNio.setNativeFileFDVMChannel(chOut, fdsArr[1],
FileChannelImpl.WRITE);
}
}
private static synchronized int nextThreadNum()
{
return ++threadNum;
}
private static String[] addMissingEnvVar(String[] env, String name,
String defValue)
{
String value = VMAccessorJavaIo.getenvPlatformVMFile(name);
if (value == null && (value = defValue) == null)
return env;
int len = env.length;
String nameAssign = name.concat("=");
String str;
for (int i = 0; i < len; i++)
if ((str = env[i]) != null && str.startsWith(nameAssign))
return env;
String[] newEnv = new String[len + 1];
VMSystem.arraycopy(env, 0, newEnv, 0, len);
newEnv[len] = nameAssign.concat(value);
return newEnv;
}
private static String convertEnv(String[] env)
{
int len = env.length;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++)
{
String envStr = env[i];
if (envStr == null)
throw new NullPointerException();
if (envStr.length() > 0)
{
int pos = envStr.indexOf('\0', 0);
sb.append(pos >= 0 ? envStr.substring(0, pos) : envStr).append('\0');
}
}
return sb.toString();
}
private static String getValidProgExtsList()
{
String validDotExts = VMSystem.getenv("PATHEXT");
return validDotExts != null || (validDotExts =
getValidProgExtsList0()) != null ? validDotExts : "";
}
private static String convertProgName(String progName, boolean changeDir)
throws IOException
{
int pos = progName.indexOf('\0', 0);
if (pos >= 0)
progName = progName.substring(0, pos);
String validDotExts = VALID_PROG_EXTS_LIST;
boolean isSearchNeeded = IS_PROG_SEARCH_NEEDED;
if (progName.length() == 0 ||
(validDotExts.length() == 0 && !isSearchNeeded))
return (new File(progName)).getPath();
String name = (new File(progName)).getName();
String searchList = isSearchNeeded && progName.equals(name) ?
VMSystem.getenv("PATH") : null;
char pathSep =
SystemProperties.getProperty("path.separator", ":").charAt(0);
if (searchList == null)
searchList = "";
if (name.lastIndexOf('.') >= 0)
validDotExts = "";
pos = searchList.length();
if (pos > 0 && searchList.charAt(pos - 1) != pathSep &&
(searchList.charAt(pos - 1) != '.' ||
(pos != 1 && searchList.charAt(pos - 2) != pathSep)))
searchList = searchList.concat(String.valueOf(pathSep)).concat(".");
if ((pos = validDotExts.length()) > 0 && validDotExts.charAt(pos - 1) !=
pathSep && validDotExts.charAt(0) != pathSep)
validDotExts = validDotExts.concat(String.valueOf(pathSep));
pos = 0;
String prefix;
File file;
String path = null;
do
{
int next = searchList.indexOf(pathSep, pos);
if (next < 0)
next = searchList.length();
prefix = searchList.substring(pos, next);
if (pos == next && searchList.length() > 0)
prefix = ".";
int pos2 = 0;
do
{
int next2 = validDotExts.indexOf(pathSep, pos2);
if (next2 < 0)
next2 = validDotExts.length();
name = progName;
if (pos2 < next2)
{
String ext = validDotExts.substring(pos2, next2);
if (ext.charAt(0) != '.')
ext = ".".concat(ext);
name = progName.concat(ext);
}
file = prefix.length() > 0 ? new File(prefix, name) : new File(name);
if (checkPermissions0(file.getPath(), 0) >= 0)
{
next = searchList.length();
path = file.getPath();
break;
}
pos2 = next2 + 1;
} while (validDotExts.length() > pos2);
pos = next + 1;
} while (searchList.length() > pos);
if (path == null)
throw new IOException("executable file not found: ".concat(progName));
if (changeDir && isSearchNeeded && !file.isAbsolute())
{
if (prefix.equals("."))
file = new File(name);
path = file.getAbsoluteFile().getPath();
}
return path;
}
private static String convertCmd(String progName, String[] cmd,
boolean isSpaceDelimited)
{
StringBuilder sb = new StringBuilder();
String cmdStr = progName;
int i = 0;
if (isSpaceDelimited)
{
do
{
if (cmdStr != null)
{
if (cmdStr.length() > 0 && cmdStr.indexOf(' ') < 0 &&
cmdStr.indexOf('\t') < 0 && cmdStr.indexOf('\n') < 0 &&
cmdStr.indexOf('\r') < 0 && cmdStr.indexOf('"') < 0)
sb.append(cmdStr);
else
{
sb.append('"');
do
{
int j = cmdStr.indexOf('"');
int k = cmdStr.indexOf('\\');
if (k >= 0 && (j < 0 || k < j))
j = k;
if (j < 0)
break;
sb.append(cmdStr.substring(0, j));
if (j == k)
{
int len = cmdStr.length();
do
{
j++;
} while (j < len && cmdStr.charAt(j) == '\\');
sb.append(cmdStr.substring(k, j));
if (j >= len || cmdStr.charAt(j) == '"')
sb.append(cmdStr.substring(k, j));
j--;
}
else sb.append('\\').append('"');
cmdStr = cmdStr.substring(j + 1);
} while (true);
sb.append(cmdStr).append('"');
}
}
if (++i >= cmd.length)
break;
if (cmdStr != null)
sb.append(' ');
cmdStr = cmd[i];
int pos = cmdStr.indexOf('\0', 0);
if (pos >= 0)
cmdStr = cmdStr.substring(0, pos);
} while (true);
sb.append('\0');
}
else
{
do
{
if (cmdStr != null)
{
if (cmdStr.length() == 0 || cmdStr.charAt(0) == ' ')
sb.append(' ');
sb.append(cmdStr).append('\0');
}
if (++i >= cmd.length)
break;
cmdStr = cmd[i];
int pos = cmdStr.indexOf('\0', 0);
if (pos >= 0)
cmdStr = cmdStr.substring(0, pos);
} while (true);
}
return sb.toString();
}
private static InputStream createInputStream(VMChannel ch)
{
return VMAccessorJavaIo.newFileInputStream(
VMAccessorGnuJavaNio.newFileChannelImpl(ch, FileChannelImpl.READ));
}
private static OutputStream createOutputStream(VMChannel ch)
{
return VMAccessorJavaIo.newFileOutputStream(
VMAccessorGnuJavaNio.newFileChannelImpl(ch, FileChannelImpl.WRITE));
}
private static native String getValidProgExtsList0();
private static native int isProgSearchNeeded0();
private static native int isCmdSpaceDelimited0();
private static native int checkPermissions0(String path, int isDirCheck);
private static native int getSpawnWorkBufSize0(String cmdZBlock,
String envZBlock, int cmdArrLen, int envArrLen);
private static native int nativeSpawn0(String progName, String cmdZBlock,
String envZBlock, String dirpath, int[] fdsArr, long[] pidArr,
byte[] workBuf, int bufLen, int redirect);
private static native int nativeWaitFor0(long pid,
int nohang); /* blocking syscall */
private static native int nativeKill(long pid);
private static native int pipe0(int[] fdsArr);
}