/* * 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()); }