/*
* The MIT License
*
* Copyright (c) 2009-, Sun Microsystems, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.gvmax.assembly.daemon;
import static com.gvmax.assembly.daemon.CLibrary.LIBC;
import static com.sun.jna.Pointer.NULL;
import static java.util.logging.Level.FINEST;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.logging.Logger;
import com.gvmax.assembly.daemon.CLibrary.FILE;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.StringArray;
import com.sun.jna.ptr.IntByReference;
/**
* List of arguments for Java VM and application.
*
* @author Kohsuke Kawaguchi
*/
public class JavaVMArguments extends ArrayList<String> {
private static final long serialVersionUID = 1L;
public JavaVMArguments() {
}
public JavaVMArguments(Collection<? extends String> c) {
super(c);
}
public void removeSystemProperty(String name) {
name = "-D" + name;
String nameeq = name + '=';
for (Iterator<String> itr = this.iterator(); itr.hasNext();) {
String s = itr.next();
if (s.equals(name) || s.startsWith(nameeq)) {
itr.remove();
}
}
}
public void setSystemProperty(String name, String value) {
removeSystemProperty(name);
// index 0 is the executable name
add(1, "-D" + name + "=" + value);
}
/**
* Removes the n items from the end. Useful for removing all the Java
* arguments to rebuild them.
*/
public void removeTail(int n) {
removeAll(subList(size() - n, size()));
}
/* package */StringArray toStringArray() {
return new StringArray(toArray(new String[size()]));
}
/**
* Gets the process argument list of the current process.
*/
public static JavaVMArguments current() throws IOException {
return of(-1);
}
/**
* Gets the process argument list of the specified process ID.
*
* @param pid
* -1 to indicate the current process.
*/
public static JavaVMArguments of(int pid) throws IOException {
String os = System.getProperty("os.name");
if ("Linux".equals(os)) {
return ofLinux(pid);
}
if ("SunOS".equals(os)) {
return ofSolaris(pid);
}
if ("Mac OS X".equals(os)) {
return ofMac(pid);
}
throw new UnsupportedOperationException("Unsupported Operating System " + os);
}
private static JavaVMArguments ofLinux(int pid) throws IOException {
pid = resolvePID(pid);
String cmdline = readFile(new File("/proc/" + pid + "/cmdline"));
JavaVMArguments args = new JavaVMArguments(Arrays.asList(cmdline.split("\0")));
// we don't want them inherited
args.removeSystemProperty(Daemon.class.getName());
return args;
}
private static int resolvePID(int pid) {
if (pid == -1) {
pid = LIBC.getpid();
}
return pid;
}
private static JavaVMArguments ofSolaris(int pid) throws IOException {
// /proc shows different contents based on the caller's memory model, so
// we need to know if we are 32 or 64.
// 32 JVMs are the norm, so err on the 32bit side.
boolean areWe64 = "64".equals(System.getProperty("sun.arch.data.model"));
pid = resolvePID(pid);
RandomAccessFile psinfo = new RandomAccessFile(new File("/proc/" + pid + "/psinfo"), "r");
try {
// see
// http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/procfs.h
// typedef struct psinfo {
// int pr_flag; /* process flags */
// int pr_nlwp; /* number of lwps in the process */
// pid_t pr_pid; /* process id */
// pid_t pr_ppid; /* process id of parent */
// pid_t pr_pgid; /* process id of process group leader */
// pid_t pr_sid; /* session id */
// uid_t pr_uid; /* real user id */
// uid_t pr_euid; /* effective user id */
// gid_t pr_gid; /* real group id */
// gid_t pr_egid; /* effective group id */
// uintptr_t pr_addr; /* address of process */
// size_t pr_size; /* size of process image in Kbytes */
// size_t pr_rssize; /* resident set size in Kbytes */
// dev_t pr_ttydev; /* controlling tty device (or PRNODEV) */
// ushort_t pr_pctcpu; /* % of recent cpu time used by all lwps */
// ushort_t pr_pctmem; /* % of system memory used by process */
// timestruc_t pr_start; /* process start time, from the epoch */
// timestruc_t pr_time; /* cpu time for this process */
// timestruc_t pr_ctime; /* cpu time for reaped children */
// char pr_fname[PRFNSZ]; /* name of exec'ed file */
// char pr_psargs[PRARGSZ]; /* initial characters of arg list */
// int pr_wstat; /* if zombie, the wait() status */
// int pr_argc; /* initial argument count */
// uintptr_t pr_argv; /* address of initial argument vector */
// uintptr_t pr_envp; /* address of initial environment vector */
// char pr_dmodel; /* data model of the process */
// lwpsinfo_t pr_lwp; /* information for representative lwp */
// } psinfo_t;
// see
// http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/types.h
// for the size of the various datatype.
// see
// http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/cmd/ptools/pargs/pargs.c
// for how to read this information
psinfo.seek(8);
if (adjust(psinfo.readInt()) != pid) {
throw new IOException("psinfo PID mismatch"); // sanity check
}
/*
* The following program computes the offset: #include <stdio.h>
* #include <sys/procfs.h> int main() { printf("psinfo_t = %d\n",
* sizeof(psinfo_t)); psinfo_t *x; x = 0; printf("%x\n",
* &(x->pr_argc)); }
*/
psinfo.seek(areWe64 ? 0xEC : 0xBC); // now jump to pr_argc
int argc = adjust(psinfo.readInt());
long argp = areWe64 ? adjust(psinfo.readLong()) : to64(adjust(psinfo.readInt()));
if (LOGGER.isLoggable(FINEST)) {
LOGGER.finest(String.format("argc=%d,argp=%X", argc, argp));
}
File asFile = new File("/proc/" + pid + "/as");
if (areWe64) {
// 32bit and 64bit basically does the same thing, but because
// the stream position
// is computed with signed long, doing 64bit seek to a position
// bigger than Long.MAX_VALUE
// requres some real hacking. Hence two different code path.
// (RandomAccessFile uses Java long for offset, so it just can't
// get to anywhere beyond Long.MAX_VALUE)
FILE fp = LIBC.fopen(asFile.getPath(), "r");
try {
JavaVMArguments args = new JavaVMArguments();
Memory m = new Memory(8);
for (int n = 0; n < argc; n++) {
// read a pointer to one entry
seek64(fp, argp + n * 8);
if (LOGGER.isLoggable(FINEST)) {
LOGGER.finest(String.format("Seeked to %X", LIBC.ftell(fp)));
}
m.setLong(0, 0); // just to make sure failed read won't
// result in bogus value
LIBC.fread(m, 1, 8, fp);
long p = m.getLong(0);
args.add(readLine(fp, p, "argv[" + n + "]"));
}
return args;
} finally {
LIBC.fclose(fp);
}
} else {
RandomAccessFile as = new RandomAccessFile(asFile, "r");
try {
JavaVMArguments args = new JavaVMArguments();
for (int n = 0; n < argc; n++) {
// read a pointer to one entry
as.seek(argp + n * 4);
int p = adjust(as.readInt());
args.add(readLine(as, p, "argv[" + n + "]"));
}
return args;
} finally {
as.close();
}
}
} finally {
psinfo.close();
}
}
/**
* Seek to the specified position. This method handles offset bigger than
* {@link Long#MAX_VALUE} correctly.
*
* @param upos
* This value is interpreted as unsigned 64bit integer (even
* though it's typed 'long')
*/
private static void seek64(FILE fp, long upos) {
LIBC.fseek(fp, 0, 0); // start at the beginning
while (upos < 0) {
long chunk = Long.MAX_VALUE;
upos -= chunk;
LIBC.fseek(fp, chunk, 1);
}
LIBC.fseek(fp, upos, 1);
}
/**
* {@link DataInputStream} reads a value in big-endian, so convert it to the
* correct value on little-endian systems.
*/
private static int adjust(int i) {
if (IS_LITTLE_ENDIAN) {
return (i << 24) | ((i << 8) & 0x00FF0000) | ((i >> 8) & 0x0000FF00) | (i >>> 24);
} else {
return i;
}
}
private static long adjust(long i) {
if (IS_LITTLE_ENDIAN) {
return (i << 56) | ((i << 40) & 0x00FF000000000000L) | ((i << 24) & 0x0000FF0000000000L) | ((i << 8) & 0x000000FF00000000L) | ((i >> 8) & 0x00000000FF000000L) | ((i >> 24) & 0x0000000000FF0000L) | ((i >> 40) & 0x000000000000FF00L) | (i >> 56);
} else {
return i;
}
}
/**
* int to long conversion with zero-padding.
*/
private static long to64(int i) {
return i & 0xFFFFFFFFL;
}
private static String readLine(RandomAccessFile as, int p, String prefix) throws IOException {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.finest(String.format("Reading %s at %X", prefix, p));
}
as.seek(to64(p));
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int ch, i = 0;
while ((ch = as.read()) > 0) {
if ((++i) % 100 == 0 && LOGGER.isLoggable(FINEST)) {
LOGGER.finest(prefix + " is so far " + buf.toString());
}
buf.write(ch);
}
String line = buf.toString();
if (LOGGER.isLoggable(FINEST)) {
LOGGER.finest(prefix + " was " + line);
}
return line;
}
private static String readLine(FILE as, long p, String prefix) throws IOException {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.finest(String.format("Reading %s at %X", prefix, p));
}
seek64(as, p);
Memory m = new Memory(1);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int i = 0;
while (true) {
if (LIBC.fread(m, 1, 1, as) == 0) {
break;
}
byte b = m.getByte(0);
if (b == 0) {
break;
}
if ((++i) % 100 == 0 && LOGGER.isLoggable(FINEST)) {
LOGGER.finest(prefix + " is so far " + buf.toString());
}
buf.write(b);
}
String line = buf.toString();
if (LOGGER.isLoggable(FINEST)) {
LOGGER.finest(prefix + " was " + line);
}
return line;
}
/**
* Reads the entire file.
*/
private static String readFile(File f) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileInputStream fin = new FileInputStream(f);
try {
int sz;
byte[] buf = new byte[1024];
while ((sz = fin.read(buf)) >= 0) {
baos.write(buf, 0, sz);
}
return baos.toString();
} finally {
fin.close();
}
}
/**
* Mac support
*
* See http://developer.apple.com/qa/qa2001/qa1123.html
* http://www.osxfaq.com/man/3/kvm_getprocs.ws http://matburt.net/?p=16
* (libkvm is removed from OSX) where is kinfo_proc?
* http://lists.apple.com/archives/xcode-users/2008/Mar/msg00781.html
*
* This code uses sysctl to get the arg/env list:
* http://www.psychofx.com/psi
* /trac/browser/psi/trunk/src/arch/macosx/macosx_process.c which came from
* http://www.opensource.apple.com/darwinsource/10.4.2/top-15/libtop.c
*
* sysctl is defined in libc.
*
* PS source code for Mac:
* http://www.opensource.apple.com/darwinsource/10.4.1
* /adv_cmds-79.1/ps.tproj/
*/
private static JavaVMArguments ofMac(int pid) {
// local constants
final int CTL_KERN = 1;
final int KERN_ARGMAX = 8;
final int KERN_PROCARGS2 = 49;
final int sizeOfInt = Native.getNativeSize(int.class);
IntByReference _ = new IntByReference();
IntByReference argmaxRef = new IntByReference(0);
IntByReference size = new IntByReference(sizeOfInt);
// for some reason, I was never able to get sysctlbyname work.
// if(LIBC.sysctlbyname("kern.argmax", argmaxRef.getPointer(), size,
// NULL, _)!=0)
if (LIBC.sysctl(new int[] { CTL_KERN, KERN_ARGMAX }, 2, argmaxRef.getPointer(), size, NULL, _) != 0) {
throw new UnsupportedOperationException("Failed to get kernl.argmax: " + LIBC.strerror(Native.getLastError()));
}
int argmax = argmaxRef.getValue();
LOGGER.fine("argmax=" + argmax);
class StringArrayMemory extends Memory {
private long offset = 0;
StringArrayMemory(long l) {
super(l);
}
int readInt() {
int r = getInt(offset);
offset += sizeOfInt;
return r;
}
@SuppressWarnings("unused")
byte peek() {
return getByte(offset);
}
String readString() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte ch;
while ((ch = getByte(offset++)) != '\0') {
baos.write(ch);
}
return baos.toString();
}
void skip0() {
// skip trailing '\0's
while (getByte(offset) == '\0') {
offset++;
}
}
}
StringArrayMemory m = new StringArrayMemory(argmax);
size.setValue(argmax);
if (LIBC.sysctl(new int[] { CTL_KERN, KERN_PROCARGS2, resolvePID(pid) }, 3, m, size, NULL, _) != 0) {
throw new UnsupportedOperationException("Failed to obtain ken.procargs2: " + LIBC.strerror(Native.getLastError()));
}
/*
* Make a sysctl() call to get the raw argument space of the process.
* The layout is documented in start.s, which is part of the Csu
* project. In summary, it looks like:
*
* /---------------\ 0x00000000 : : : : |---------------| | argc |
* |---------------| | arg[0] | |---------------| : : : :
* |---------------| | arg[argc - 1] | |---------------| | 0 |
* |---------------| | env[0] | |---------------| : : : :
* |---------------| | env[n] | |---------------| | 0 |
* |---------------| <-- Beginning of data returned by sysctl() |
* exec_path | is here. |:::::::::::::::| | | | String area. | | |
* |---------------| <-- Top of stack. : : : : \---------------/
* 0xffffffff
*/
JavaVMArguments args = new JavaVMArguments();
int nargs = m.readInt();
m.readString(); // exec path
for (int i = 0; i < nargs; i++) {
m.skip0();
args.add(m.readString());
}
// this is how you can read environment variables
// List<String> lst = new ArrayList<String>();
// while(m.peek()!=0)
// lst.add(m.readString());
return args;
}
private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian"));
private static final Logger LOGGER = Logger.getLogger(JavaVMArguments.class.getName());
}