View Javadoc
1   /*
2    * The MIT License
3    *
4    * Copyright (c) 2009-, Sun Microsystems, Inc.
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in
14   * all copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22   * THE SOFTWARE.
23   */
24  package com.sun.akuma;
25  
26  import com.sun.jna.StringArray;
27  import com.sun.jna.Memory;
28  import com.sun.jna.Native;
29  import static com.sun.jna.Pointer.NULL;
30  import com.sun.jna.ptr.IntByReference;
31  
32  import java.util.*;
33  import static java.util.logging.Level.FINEST;
34  import java.util.logging.Logger;
35  import java.util.logging.Level;
36  import java.util.logging.ConsoleHandler;
37  import java.io.IOException;
38  import java.io.FileInputStream;
39  import java.io.File;
40  import java.io.ByteArrayOutputStream;
41  import java.io.RandomAccessFile;
42  import java.io.DataInputStream;
43  
44  import static com.sun.akuma.CLibrary.LIBC;
45  import com.sun.akuma.CLibrary.FILE;
46  
47  /**
48   * List of arguments for Java VM and application.
49   *
50   * @author Kohsuke Kawaguchi
51   */
52  public class JavaVMArguments extends ArrayList<String> {
53      public JavaVMArguments() {
54      }
55  
56      public JavaVMArguments(Collection<? extends String> c) {
57          super(c);
58      }
59  
60      public void removeSystemProperty(String name) {
61          name = "-D"+name;
62          String nameeq = name+'=';
63          for (Iterator<String> itr = this.iterator(); itr.hasNext();) {
64              String s =  itr.next();
65              if(s.equals(name) || s.startsWith(nameeq))
66                  itr.remove();
67          }
68      }
69  
70      public void setSystemProperty(String name, String value) {
71          removeSystemProperty(name);
72          // index 0 is the executable name
73          add(1,"-D"+name+"="+value);
74      }
75  
76      /**
77       * Removes the n items from the end.
78       * Useful for removing all the Java arguments to rebuild them.
79       */
80      public void removeTail(int n) {
81          removeAll(subList(size()-n,size()));
82      }
83  
84      /*package*/ StringArray toStringArray() {
85          return new StringArray(toArray(new String[size()]));
86      }
87  
88      /**
89       * Gets the process argument list of the current process.
90       */
91      public static JavaVMArguments current() throws IOException {
92          return of(-1);
93      }
94  
95      /**
96       * Gets the process argument list of the specified process ID.
97       *
98       * @param pid
99       *      -1 to indicate the current process.
100      */
101     public static JavaVMArguments of(int pid) throws IOException {
102         String os = System.getProperty("os.name");
103         if("Linux".equals(os))
104             return ofLinux(pid);
105         if("SunOS".equals(os))
106             return ofSolaris(pid);
107         if("Mac OS X".equals(os))
108             return ofMac(pid);
109         if("FreeBSD".equals(os))
110             return ofFreeBSD(pid);
111 
112         throw new UnsupportedOperationException("Unsupported Operating System "+os);
113     }
114 
115     private static JavaVMArguments ofLinux(int pid) throws IOException {
116         pid = resolvePID(pid);
117 
118         String cmdline = readFile(new File("/proc/" + pid + "/cmdline"));
119         JavaVMArguments args = new JavaVMArguments(Arrays.asList(cmdline.split("\0")));
120 
121         // we don't want them inherited
122         args.removeSystemProperty(Daemon.class.getName());
123         args.removeSystemProperty(NetworkServer.class.getName()+".mode");
124         return args;
125     }
126 
127     private static int resolvePID(int pid) {
128         if(pid==-1) pid=LIBC.getpid();
129         return pid;
130     }
131 
132     private static JavaVMArguments ofSolaris(int pid) throws IOException {
133         // /proc shows different contents based on the caller's memory model, so we need to know if we are 32 or 64.
134         // 32 JVMs are the norm, so err on the 32bit side.
135         boolean areWe64 = "64".equals(System.getProperty("sun.arch.data.model"));
136 
137         pid = resolvePID(pid);
138         RandomAccessFile psinfo = new RandomAccessFile(new File("/proc/"+pid+"/psinfo"),"r");
139         try {
140             // see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/procfs.h
141             //typedef struct psinfo {
142             //	int	pr_flag;	/* process flags */
143             //	int	pr_nlwp;	/* number of lwps in the process */
144             //	pid_t	pr_pid;	/* process id */
145             //	pid_t	pr_ppid;	/* process id of parent */
146             //	pid_t	pr_pgid;	/* process id of process group leader */
147             //	pid_t	pr_sid;	/* session id */
148             //	uid_t	pr_uid;	/* real user id */
149             //	uid_t	pr_euid;	/* effective user id */
150             //	gid_t	pr_gid;	/* real group id */
151             //	gid_t	pr_egid;	/* effective group id */
152             //	uintptr_t	pr_addr;	/* address of process */
153             //	size_t	pr_size;	/* size of process image in Kbytes */
154             //	size_t	pr_rssize;	/* resident set size in Kbytes */
155             //	dev_t	pr_ttydev;	/* controlling tty device (or PRNODEV) */
156             //	ushort_t	pr_pctcpu;	/* % of recent cpu time used by all lwps */
157             //	ushort_t	pr_pctmem;	/* % of system memory used by process */
158             //	timestruc_t	pr_start;	/* process start time, from the epoch */
159             //	timestruc_t	pr_time;	/* cpu time for this process */
160             //	timestruc_t	pr_ctime;	/* cpu time for reaped children */
161             //	char	pr_fname[PRFNSZ];	/* name of exec'ed file */
162             //	char	pr_psargs[PRARGSZ];	/* initial characters of arg list */
163             //	int	pr_wstat;	/* if zombie, the wait() status */
164             //	int	pr_argc;	/* initial argument count */
165             //	uintptr_t	pr_argv;	/* address of initial argument vector */
166             //	uintptr_t	pr_envp;	/* address of initial environment vector */
167             //	char	pr_dmodel;	/* data model of the process */
168             //	lwpsinfo_t	pr_lwp;	/* information for representative lwp */
169             //} psinfo_t;
170 
171             // see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/types.h
172             // for the size of the various datatype.
173 
174             // see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/cmd/ptools/pargs/pargs.c
175             // for how to read this information
176 
177             psinfo.seek(8);
178             if(adjust(psinfo.readInt())!=pid)
179                 throw new IOException("psinfo PID mismatch");   // sanity check
180 
181             /* The following program computes the offset:
182                     #include <stdio.h>
183                     #include <sys/procfs.h>
184                     int main() {
185                       printf("psinfo_t = %d\n", sizeof(psinfo_t));
186                       psinfo_t *x;
187                       x = 0;
188                       printf("%x\n", &(x->pr_argc));
189                     }
190              */
191 
192             psinfo.seek(areWe64?0xEC:0xBC);  // now jump to pr_argc
193             int argc = adjust(psinfo.readInt());
194             long argp = areWe64?adjust(psinfo.readLong()):to64(adjust(psinfo.readInt()));
195             if(LOGGER.isLoggable(FINEST))
196                 LOGGER.finest(String.format("argc=%d,argp=%X",argc,argp));
197 
198             File asFile = new File("/proc/" + pid + "/as");
199             if (areWe64) {
200                 // 32bit and 64bit basically does the same thing, but because the stream position
201                 // is computed with signed long, doing 64bit seek to a position bigger than Long.MAX_VALUE
202                 // requres some real hacking. Hence two different code path.
203                 // (RandomAccessFile uses Java long for offset, so it just can't get to anywhere beyond Long.MAX_VALUE)
204                 FILE fp = LIBC.fopen(asFile.getPath(),"r");
205                 try {
206                     JavaVMArguments args = new JavaVMArguments();
207                     Memory m = new Memory(8);
208                     for( int n=0; n<argc; n++ ) {
209                         // read a pointer to one entry
210                         seek64(fp,argp+n*8);
211                         if(LOGGER.isLoggable(FINEST))
212                             LOGGER.finest(String.format("Seeked to %X",LIBC.ftell(fp)));
213 
214                         m.setLong(0,0); // just to make sure failed read won't result in bogus value
215                         LIBC.fread(m,1,8,fp);
216                         long p = m.getLong(0);
217 
218                         args.add(readLine(fp, p, "argv["+ n +"]"));
219                     }
220                     return args;
221                 } finally {
222                     LIBC.fclose(fp);
223                 }
224             } else {
225                 RandomAccessFile as = new RandomAccessFile(asFile,"r");
226                 try {
227                     JavaVMArguments args = new JavaVMArguments();
228                     for( int n=0; n<argc; n++ ) {
229                         // read a pointer to one entry
230                         as.seek(argp+n*4);
231                         int p = adjust(as.readInt());
232 
233                         args.add(readLine(as, p, "argv["+ n +"]"));
234                     }
235                     return args;
236                 } finally {
237                     as.close();
238                 }
239             }
240         } finally {
241             psinfo.close();
242         }
243     }
244 
245     /**
246      * Seek to the specified position. This method handles offset bigger than {@link Long#MAX_VALUE} correctly.
247      *
248      * @param upos
249      *      This value is interpreted as unsigned 64bit integer (even though it's typed 'long')
250      */
251     private static void seek64(FILE fp, long upos) {
252         LIBC.fseek(fp,0,0); // start at the beginning
253         while(upos<0) {
254             long chunk = Long.MAX_VALUE;
255             upos -= chunk;
256             LIBC.fseek(fp,chunk,1);
257         }
258         LIBC.fseek(fp,upos,1);
259     }
260 
261     /**
262      * {@link DataInputStream} reads a value in big-endian, so
263      * convert it to the correct value on little-endian systems.
264      */
265     private static int adjust(int i) {
266         if(IS_LITTLE_ENDIAN)
267             return (i<<24) |((i<<8) & 0x00FF0000) | ((i>>8) & 0x0000FF00) | (i>>>24);
268         else
269             return i;
270     }
271 
272     private static long adjust(long i) {
273         if(IS_LITTLE_ENDIAN)
274             return (i<<56)
275                     | ((i<<40) & 0x00FF000000000000L)
276                     | ((i<<24) & 0x0000FF0000000000L)
277                     | ((i<< 8) & 0x000000FF00000000L)
278                     | ((i>> 8) & 0x00000000FF000000L)
279                     | ((i>>24) & 0x0000000000FF0000L)
280                     | ((i>>40) & 0x000000000000FF00L)
281                     | (i>>56);
282         else
283             return i;
284     }
285 
286     /**
287      * int to long conversion with zero-padding.
288      */
289     private static long to64(int i) {
290         return i&0xFFFFFFFFL;
291     }
292 
293     private static String readLine(RandomAccessFile as, int p, String prefix) throws IOException {
294         if(LOGGER.isLoggable(FINEST))
295             LOGGER.finest(String.format("Reading %s at %X",prefix,p));
296 
297         as.seek(to64(p));
298         ByteArrayOutputStream buf = new ByteArrayOutputStream();
299         int ch,i=0;
300         while((ch=as.read())>0) {
301             if((++i)%100==0 && LOGGER.isLoggable(FINEST))
302                 LOGGER.finest(prefix +" is so far "+buf.toString());
303 
304             buf.write(ch);
305         }
306         String line = buf.toString();
307         if(LOGGER.isLoggable(FINEST))
308             LOGGER.finest(prefix+" was "+line);
309         return line;
310     }
311 
312     private static String readLine(FILE as, long p, String prefix) throws IOException {
313         if(LOGGER.isLoggable(FINEST))
314             LOGGER.finest(String.format("Reading %s at %X",prefix,p));
315 
316         seek64(as,p);
317         Memory m = new Memory(1);
318         ByteArrayOutputStream buf = new ByteArrayOutputStream();
319         int i=0;
320         while(true) {
321             if(LIBC.fread(m,1,1,as)==0)   break;
322             byte b = m.getByte(0);
323             if(b==0)    break;
324 
325             if((++i)%100==0 && LOGGER.isLoggable(FINEST))
326                 LOGGER.finest(prefix +" is so far "+buf.toString());
327 
328             buf.write(b);
329         }
330         String line = buf.toString();
331         if(LOGGER.isLoggable(FINEST))
332             LOGGER.finest(prefix+" was "+line);
333         return line;
334     }
335 
336     /**
337      * Reads the entire file.
338      */
339     private static String readFile(File f) throws IOException {
340         ByteArrayOutputStream baos = new ByteArrayOutputStream();
341         FileInputStream fin = new FileInputStream(f);
342         try {
343             int sz;
344             byte[] buf = new byte[1024];
345 
346             while((sz=fin.read(buf))>=0) {
347                 baos.write(buf,0,sz);
348             }
349 
350             return baos.toString();
351         } finally {
352             fin.close();
353         }
354     }
355 
356     /**
357      * Mac support
358      *
359      * See http://developer.apple.com/qa/qa2001/qa1123.html
360      * http://www.osxfaq.com/man/3/kvm_getprocs.ws
361      * http://matburt.net/?p=16 (libkvm is removed from OSX)
362      * where is kinfo_proc? http://lists.apple.com/archives/xcode-users/2008/Mar/msg00781.html
363      *
364      * This code uses sysctl to get the arg/env list:
365      * http://www.psychofx.com/psi/trac/browser/psi/trunk/src/arch/macosx/macosx_process.c
366      * which came from
367      * http://www.opensource.apple.com/darwinsource/10.4.2/top-15/libtop.c
368      *
369      * sysctl is defined in libc.
370      *
371      * PS source code for Mac:
372      * http://www.opensource.apple.com/darwinsource/10.4.1/adv_cmds-79.1/ps.tproj/
373      */
374     private static JavaVMArguments ofMac(int pid) {
375         // local constants
376         final int CTL_KERN = 1;
377         final int KERN_ARGMAX = 8;
378         final int KERN_PROCARGS2 = 49;
379         final int sizeOfInt = Native.getNativeSize(int.class);
380         IntByReference _ = new IntByReference();
381 
382 
383         IntByReference argmaxRef = new IntByReference(0);
384         IntByReference size = new IntByReference(sizeOfInt);
385 
386         // for some reason, I was never able to get sysctlbyname work.
387 //        if(LIBC.sysctlbyname("kern.argmax", argmaxRef.getPointer(), size, NULL, _)!=0)
388         if(LIBC.sysctl(new int[]{CTL_KERN,KERN_ARGMAX},2, argmaxRef.getPointer(), size, NULL, _)!=0)
389             throw new UnsupportedOperationException("Failed to get kernl.argmax: "+LIBC.strerror(Native.getLastError()));
390 
391         int argmax = argmaxRef.getValue();
392         LOGGER.fine("argmax="+argmax);
393 
394         class StringArrayMemory extends Memory {
395             private long offset=0;
396 
397             StringArrayMemory(long l) {
398                 super(l);
399             }
400 
401             int readInt() {
402                 int r = getInt(offset);
403                 offset+=sizeOfInt;
404                 return r;
405             }
406 
407             byte peek() {
408                 return getByte(offset);
409             }
410 
411             String readString() {
412                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
413                 byte ch;
414                 while((ch = getByte(offset++))!='\0')
415                     baos.write(ch);
416                 return baos.toString();
417             }
418 
419             void skip0() {
420                 // skip trailing '\0's
421                 while(getByte(offset)=='\0')
422                     offset++;
423             }
424         }
425         StringArrayMemory m = new StringArrayMemory(argmax);
426         size.setValue(argmax);
427         if(LIBC.sysctl(new int[]{CTL_KERN,KERN_PROCARGS2,resolvePID(pid)},3, m, size, NULL, _)!=0)
428             throw new UnsupportedOperationException("Failed to obtain ken.procargs2: "+LIBC.strerror(Native.getLastError()));
429 
430         
431         /*
432          * Make a sysctl() call to get the raw argument space of the
433          * process.  The layout is documented in start.s, which is part
434          * of the Csu project.  In summary, it looks like:
435          *
436          * /---------------\ 0x00000000
437          * :               :
438          * :               :
439          * |---------------|
440          * | argc          |
441          * |---------------|
442          * | arg[0]        |
443          * |---------------|
444          * :               :
445          * :               :
446          * |---------------|
447          * | arg[argc - 1] |
448          * |---------------|
449          * | 0             |
450          * |---------------|
451          * | env[0]        |
452          * |---------------|
453          * :               :
454          * :               :
455          * |---------------|
456          * | env[n]        |
457          * |---------------|
458          * | 0             |
459          * |---------------| <-- Beginning of data returned by sysctl()
460          * | exec_path     |     is here.
461          * |:::::::::::::::|
462          * |               |
463          * | String area.  |
464          * |               |
465          * |---------------| <-- Top of stack.
466          * :               :
467          * :               :
468          * \---------------/ 0xffffffff
469          */
470 
471         JavaVMArguments args = new JavaVMArguments();
472         int nargs = m.readInt();
473         m.readString(); // exec path
474         for( int i=0; i<nargs; i++) {
475             m.skip0();
476             args.add(m.readString());
477         }
478 
479         // this is how you can read environment variables
480 //        List<String> lst = new ArrayList<String>();
481 //        while(m.peek()!=0)
482 //            lst.add(m.readString());
483 
484         return args;
485     }
486 
487     private static JavaVMArguments ofFreeBSD(int pid) {
488     	// taken from sys/sysctl.h
489     	final int CTL_KERN = 1;
490     	final int KERN_ARGMAX = 8;
491     	final int KERN_PROC = 14;
492     	final int KERN_PROC_ARGS = 7;
493     	
494     	IntByReference _ = new IntByReference();
495     	IntByReference sysctlArgMax = new IntByReference();
496     	IntByReference size = new IntByReference();
497     	
498     	size.setValue(4);
499     	if( LIBC.sysctl(new int[]{CTL_KERN, KERN_ARGMAX}, 2, sysctlArgMax.getPointer(), size, NULL, _) != 0)
500     		throw new UnsupportedOperationException("Failed to sysctl kern.argmax");
501     	
502     	int argmax = sysctlArgMax.getValue();
503     	Memory m = new Memory(argmax);
504         size.setValue(argmax);
505         
506     	if( LIBC.sysctl(new int[]{CTL_KERN,KERN_PROC, KERN_PROC_ARGS, resolvePID(pid)}, 4, m, size, NULL, _) != 0)
507     		throw new UnsupportedOperationException("");
508     	
509     	ByteArrayOutputStream baos = new ByteArrayOutputStream();
510     	ArrayList<String> lArgs = new ArrayList<String>();
511         byte ch;
512         int offset = 0;
513         while(offset < size.getValue()){
514 	        while((ch = m.getByte(offset++))!='\0')
515 	            baos.write(ch);
516 	        lArgs.add(baos.toString());
517 	        baos.reset();
518         }
519         
520         return new JavaVMArguments(lArgs);
521 	}
522 
523     
524     private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian"));
525 
526     private static final Logger LOGGER = Logger.getLogger(JavaVMArguments.class.getName());
527 
528     public static void main(String[] args) throws IOException {
529         // dump the process model of the caller
530         System.out.println("sun.arch.data.model="+System.getProperty("sun.arch.data.model"));
531         System.out.println("sun.cpu.endian="+System.getProperty("sun.cpu.endian"));
532         
533         LOGGER.setLevel(Level.ALL);
534         ConsoleHandler handler = new ConsoleHandler();
535         handler.setLevel(Level.ALL);
536         LOGGER.addHandler(handler);
537         
538         if (args.length==0)
539             System.out.println(current());
540         else {
541             for (String arg : args) {
542                 System.out.println(of(Integer.valueOf(arg)));
543             }
544         }
545     }
546 }