pax_global_header00006660000000000000000000000064117257247250014526gustar00rootroot0000000000000052 comment=c5d29d216b8f2291976312f719c18d79d48183d8 kohsuke-akuma-1bf91fd/000077500000000000000000000000001172572472500147715ustar00rootroot00000000000000kohsuke-akuma-1bf91fd/pom.xml000066400000000000000000000047431172572472500163160ustar00rootroot00000000000000 4.0.0 org.kohsuke pom 2 akuma 1.8 Embeddable daemonization library scm:git:git@github.com/kohsuke/${project.artifactId}.git scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git http://${project.artifactId}.kohsuke.org/ github-pages gitsite:git@github.com/kohsuke/${project.artifactId}.git maven-assembly-plugin attached package jar-with-dependencies com.sun.akuma.EchoServer net.java.dev.jna jna 3.4.0 maven-jxr-plugin m.g.o-public http://maven.glassfish.org/content/groups/public/ m.g.o-public http://maven.glassfish.org/content/groups/public/ MIT license http://www.opensource.org/licenses/mit-license.php repo kohsuke-akuma-1bf91fd/src/000077500000000000000000000000001172572472500155605ustar00rootroot00000000000000kohsuke-akuma-1bf91fd/src/main/000077500000000000000000000000001172572472500165045ustar00rootroot00000000000000kohsuke-akuma-1bf91fd/src/main/java/000077500000000000000000000000001172572472500174255ustar00rootroot00000000000000kohsuke-akuma-1bf91fd/src/main/java/com/000077500000000000000000000000001172572472500202035ustar00rootroot00000000000000kohsuke-akuma-1bf91fd/src/main/java/com/sun/000077500000000000000000000000001172572472500210105ustar00rootroot00000000000000kohsuke-akuma-1bf91fd/src/main/java/com/sun/akuma/000077500000000000000000000000001172572472500221065ustar00rootroot00000000000000kohsuke-akuma-1bf91fd/src/main/java/com/sun/akuma/CLibrary.java000066400000000000000000000065151172572472500244670ustar00rootroot00000000000000/* * 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.sun.akuma; import com.sun.jna.Library; import com.sun.jna.Memory; import com.sun.jna.NativeLong; import com.sun.jna.StringArray; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.PointerType; import com.sun.jna.ptr.IntByReference; /** * GNU C library. */ public interface CLibrary extends Library { int fork(); int kill(int pid, int signum); int setsid(); int setuid(short newuid); int setgid(short newgid); int umask(int mask); int getpid(); int getppid(); int chdir(String dir); int execv(String file, StringArray args); int setenv(String name, String value); int unsetenv(String name); void perror(String msg); String strerror(int errno); // this is listed in http://developer.apple.com/DOCUMENTATION/Darwin/Reference/ManPages/man3/sysctlbyname.3.html // but not in http://www.gnu.org/software/libc/manual/html_node/System-Parameters.html#index-sysctl-3493 // perhaps it is only supported on BSD? int sysctlbyname(String name, Pointer oldp, IntByReference oldlenp, Pointer newp, IntByReference newlen); int sysctl(int[] mib, int nameLen, Pointer oldp, IntByReference oldlenp, Pointer newp, IntByReference newlen); int sysctlnametomib(String name, Pointer mibp, IntByReference size); public class FILE extends PointerType { public FILE() { } public FILE(Pointer pointer) { super(pointer); } } // Additional C functions we need on Solaris 64bit to seek to a place above Long.MAX_VALUE FILE fopen(String fileName, String mode); int fseek(FILE file, long offset, int whence); long ftell(FILE file); int fread(Pointer buf, int size, int count, FILE file); int fclose(FILE file); /** * Read a symlink. The name will be copied into the specified memory, and returns the number of * bytes copied. The string is not null-terminated. * * @return * if the return value equals size, the caller needs to retry with a bigger buffer. * If -1, error. */ int readlink(String filename, Memory buffer, NativeLong size); public static final CLibrary LIBC = (CLibrary) Native.loadLibrary("c",CLibrary.class); } kohsuke-akuma-1bf91fd/src/main/java/com/sun/akuma/Daemon.java000066400000000000000000000212601172572472500241550ustar00rootroot00000000000000/* * The MIT License * * Copyright (c) 2009-, Sun Microsystems, Inc., CloudBees, 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.sun.akuma; import com.sun.jna.Memory; import com.sun.jna.Native; import com.sun.jna.NativeLong; import com.sun.jna.StringArray; import static com.sun.akuma.CLibrary.LIBC; import java.io.FileWriter; import java.io.IOException; import java.io.File; import java.util.logging.Level; import java.util.logging.Logger; /** * Forks a copy of the current process into the background. * *

* Because of the fork/exec involved in doing this, your code has to call Daemonizer in a certain sequence. * Specifically, from your main method: *

 * public static void main(String[] args) {
 *     Daemon d = new Daemon();
 *     if(d.isDaemonized()) {
 *         // perform initialization as a daemon
 *         // this involves in closing file descriptors, recording PIDs, etc.
 *         d.{@linkplain #init() init}();
 *     } else {
 *         // if you are already daemonized, no point in daemonizing yourself again,
 *         // so do this only when you aren't daemonizing.
 *         if(you decide to launch a copy into background) {
 *             d.daemonize(...);
 *             System.exit(0);
 *         }
 *     }
 *
 *     // your normal main code follows
 *     // this part can be executed in two ways
 *     // 1) the user runs your process in the foreground
 *     // 2) you decided to daemonize yourself, in which case the newly forked daemon will execute this code,
 *     //    while the originally executed foreground Java process exits before it gets here.
 *     ...
 * }
 * 
* *

* Alternatively, your main class can extend from Daemon, so that you can customize some of the behaviors. * * @author Kohsuke Kawaguchi */ public class Daemon { /** * Do all the necessary steps in one go. * * @param daemonize * Parse the command line arguments and if the application should be * daemonized, pass in true. */ public void all(boolean daemonize) throws Exception { if(isDaemonized()) init(); else { if(daemonize) { daemonize(); System.exit(0); } } } /** * Returns true if the current process is already launched as a daemon * via {@link #daemonize()}. */ public boolean isDaemonized() { return System.getProperty(Daemon.class.getName())!=null; } /** * Relaunches the JVM with the exact same arguments into the daemon. */ public void daemonize() throws IOException { daemonize(JavaVMArguments.current()); } /** * Relaunches the JVM with the given arguments into the daemon. */ public void daemonize(JavaVMArguments args) { if(isDaemonized()) throw new IllegalStateException("Already running as a daemon"); // let the child process now that it's a daemon args.setSystemProperty(Daemon.class.getName(),"daemonized"); // prepare for a fork String exe = getCurrentExecutable(); StringArray sa = args.toStringArray(); int i = LIBC.fork(); if(i<0) { LIBC.perror("initial fork failed"); System.exit(-1); } if(i==0) { // with fork, we lose all the other critical threads, to exec to Java again LIBC.execv(exe,sa); System.err.println("exec failed"); LIBC.perror("initial exec failed"); System.exit(-1); } // parent exits } /** * Overwrites the current process with a new Java VM with the given JVM arguments. */ public static void selfExec(JavaVMArguments args) { LIBC.execv(getCurrentExecutable(), args.toStringArray()); } /** * Prepares the current process to act as a daemon. * The daemon's PID is written to the file /var/run/daemon.pid. */ public void init() throws Exception { init("/var/run/daemon.pid"); } /** * Prepares the current process to act as a daemon. * @param pidFile the filename to which the daemon's PID is written; * or, null to skip writing a PID file. */ @SuppressWarnings({"OctalInteger"}) public void init(String pidFile) throws Exception { // start a new process session LIBC.setsid(); closeDescriptors(); chdirToRoot(); if (pidFile != null) writePidFile(pidFile); } /** * Closes inherited file descriptors. * *

* This method can be overridden to no-op in a subtype. Useful for debugging daemon processes * when they don't work correctly. */ protected void closeDescriptors() throws IOException { if(!Boolean.getBoolean(Daemon.class.getName()+".keepDescriptors")) { System.out.close(); System.err.close(); System.in.close(); } // ideally we'd like to close all other descriptors, but that would close // jar files used as classpath, and break JVM. } /** * change directory to '/' to avoid locking directories. */ protected void chdirToRoot() { LIBC.chdir("/"); System.setProperty("user.dir","/"); } /** * Writes out the PID of the current process to the specified file. * @param pidFile the filename to write the PID to. */ protected void writePidFile(String pidFile) throws IOException { try { FileWriter fw = new FileWriter(pidFile); fw.write(String.valueOf(LIBC.getpid())); fw.close(); } catch (IOException e) { // if failed to write, keep going because maybe we are run from non-root } } /** * Gets the current executable name. */ public static String getCurrentExecutable() { int pid = LIBC.getpid(); String name = "/proc/" + pid + "/exe"; File exe = new File(name); if(exe.exists()) { try { String path = resolveSymlink(exe); if (path!=null) return path; } catch (IOException e) { LOGGER.log(Level.FINE,"Failed to resolve symlink "+exe,e); } return name; } // cross-platform fallback return System.getProperty("java.home")+"/bin/java"; } private static String resolveSymlink(File link) throws IOException { String filename = link.getAbsolutePath(); for (int sz=512; sz < 65536; sz*=2) { Memory m = new Memory(sz); int r = LIBC.readlink(filename,m,new NativeLong(sz)); if (r<0) { int err = Native.getLastError(); if (err==22/*EINVAL --- but is this really portable?*/) return null; // this means it's not a symlink throw new IOException("Failed to readlink "+link+" error="+ err+" "+ LIBC.strerror(err)); } if (r==sz) continue; // buffer too small byte[] buf = new byte[r]; m.read(0,buf,0,r); return new String(buf); } throw new IOException("Failed to readlink "+link); } /** * Flavor of {@link Daemon} that doesn't change the current directory. * *

* This turns out to be often more useful as JavaVM can take lot of arguments and system properties * that use paths, and when we CD they won't work. */ public static class WithoutChdir extends Daemon { @Override protected void chdirToRoot() { // noop } } private static final Logger LOGGER = Logger.getLogger(Daemon.class.getName()); } kohsuke-akuma-1bf91fd/src/main/java/com/sun/akuma/EchoServer.java000066400000000000000000000056711172572472500250270ustar00rootroot00000000000000/* * 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.sun.akuma; import java.net.ServerSocket; import java.net.Socket; import java.io.InputStream; import java.io.OutputStream; import static com.sun.akuma.CLibrary.LIBC; /** * Sample echo server. * * @author Kohsuke Kawaguchi */ public class EchoServer extends NetworkServer { public static void main(String[] args) throws Exception { new EchoServer(args).run(); } public EchoServer(String[] args) { super(args); } /** * Daemonize if something is given as arguments. */ @Override protected boolean shouldBeDaemonized() { return !arguments.isEmpty(); } @Override protected void frontend() throws Exception { System.out.println("This is a simple echo server. Run with some argument to fork into a daemon, then try 'nc localhost 12345' from several terminals."); super.frontend(); } @Override protected void forkWorkers(JavaVMArguments args) throws Exception { // TODO: parse arguments and decide how many to fork forkWorkerThreads(args, 2); } @Override protected ServerSocket createServerSocket() throws Exception { System.out.println("Listening on port 12345"); // TODO: parse arguments and decide port return new ServerSocket(12345); } @Override protected void worker(ServerSocket ss) throws Exception { byte[] buf = new byte[1024]; // run a simple echo server while(true) { Socket s = ss.accept(); System.out.println("PID:"+ LIBC.getpid()+" accepted a new connection"); InputStream in = s.getInputStream(); OutputStream out = s.getOutputStream(); int len; while((len=in.read(buf))>=0) out.write(buf,0,len); s.close(); } } } kohsuke-akuma-1bf91fd/src/main/java/com/sun/akuma/JavaVMArguments.java000066400000000000000000000443461172572472500257760ustar00rootroot00000000000000/* * 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.sun.akuma; import com.sun.jna.StringArray; import com.sun.jna.Memory; import com.sun.jna.Native; import static com.sun.jna.Pointer.NULL; import com.sun.jna.ptr.IntByReference; import java.util.*; import static java.util.logging.Level.FINEST; import java.util.logging.Logger; import java.util.logging.Level; import java.util.logging.ConsoleHandler; import java.io.IOException; import java.io.FileInputStream; import java.io.File; import java.io.ByteArrayOutputStream; import java.io.RandomAccessFile; import java.io.DataInputStream; import static com.sun.akuma.CLibrary.LIBC; import com.sun.akuma.CLibrary.FILE; /** * List of arguments for Java VM and application. * * @author Kohsuke Kawaguchi */ public class JavaVMArguments extends ArrayList { public JavaVMArguments() { } public JavaVMArguments(Collection c) { super(c); } public void removeSystemProperty(String name) { name = "-D"+name; String nameeq = name+'='; for (Iterator 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()); args.removeSystemProperty(NetworkServer.class.getName()+".mode"); 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 #include 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>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; } 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 lst = new ArrayList(); // 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()); public static void main(String[] args) throws IOException { // dump the process model of the caller System.out.println("sun.arch.data.model="+System.getProperty("sun.arch.data.model")); System.out.println("sun.cpu.endian="+System.getProperty("sun.cpu.endian")); LOGGER.setLevel(Level.ALL); ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.ALL); LOGGER.addHandler(handler); if (args.length==0) System.out.println(current()); else { for (String arg : args) { System.out.println(of(Integer.valueOf(arg))); } } } } kohsuke-akuma-1bf91fd/src/main/java/com/sun/akuma/NetworkServer.java000066400000000000000000000212501172572472500255710ustar00rootroot00000000000000/* * 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.sun.akuma; import com.sun.jna.StringArray; import java.io.FileDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.SocketImpl; import java.util.logging.Logger; import java.util.List; import java.util.Collections; import java.util.Arrays; import static com.sun.akuma.CLibrary.LIBC; import sun.misc.Signal; import sun.misc.SignalHandler; /** * Multi-process network server that accepts connections on the same TCP port. * *

* This class lets you write a Unix-like multi-process network daemon. The first process acts * as the frontend. This creates a new socket, then fork several worker processes, which inherits * this socket. * *

* Worker threads will all accept connections on this port, so even when one of the worker processes * die off, your clients won't notice that there's a problem. * *

* The user of this class needs to override this class and implement abstract methods. * Several protected methods can be also overridden to customize the behaviors. * See {@link EchoServer} source code as an example. * *

* This class also inherits from {@link Daemon} to support the daemonization. * *

* From your main method, call into {@link #run()} method. Depending on whether the current process * is started as a front end or a worker process, the run method behave accordingly. * * @author Kohsuke Kawaguchi */ public abstract class NetworkServer extends Daemon { /** * Java arguments. */ protected final List arguments; protected NetworkServer(String[] args) { this.arguments = Collections.unmodifiableList(Arrays.asList(args)); } /** * Entry point. Should be called from your main method. */ public void run() throws Exception { String mode = System.getProperty(MODE_PROPERTY); if("worker".equals(mode)) { // worker process worker(); } else { // to run the frontend in the foreground if(isDaemonized()) { // running as a daemon init(); } else { // running in the foreground if(shouldBeDaemonized()) { // to launch the whole thing into a daemon daemonize(); System.exit(0); } } frontend(); } } /** * Determine if we should daemonize ourselves. */ protected boolean shouldBeDaemonized() { return !arguments.isEmpty() && arguments.get(0).equals("daemonize"); } /** * Front-end. */ protected void frontend() throws Exception { ServerSocket ss = createServerSocket(); int fdn = getUnixFileDescriptor(ss); LOGGER.fine("Listening to port "+ss.getLocalPort()+" (fd="+fdn+")"); // prepare the parameters for the exec. JavaVMArguments forkArgs = JavaVMArguments.current(); forkArgs.setSystemProperty(NetworkServer.class.getName()+".port",String.valueOf(fdn)); forkWorkers(forkArgs); } /** * Forks the worker thread with the given JVM args. * * The implementation is expected to modify the arguments to suit their need, * then call into {@link #forkWorkerThreads(JavaVMArguments, int)}. */ protected abstract void forkWorkers(JavaVMArguments args) throws Exception; /** * Called by the front-end code to fork a number of worker processes into the background. * * This method never returns. */ protected void forkWorkerThreads(JavaVMArguments arguments, int n) throws Exception { String exe = Daemon.getCurrentExecutable(); arguments.setSystemProperty(MODE_PROPERTY,"worker"); // the forked process should run as workers LOGGER.fine("Forking worker: "+arguments); StringArray sa = arguments.toStringArray(); // fork several worker processes for( int i=0; i< n; i++ ) { int r = LIBC.fork(); if(r<0) { LIBC.perror("forking a worker process failed"); System.exit(-1); } if(r==0) { // newly created child will exec to itself to get the proper Java environment back LIBC.execv(exe,sa); System.err.println("exec failed"); LIBC.perror("initial exec failed"); System.exit(-1); } } // when we are killed, kill all the worker processes, too. Signal.handle(new Signal("TERM"), new SignalHandler() { public void handle(Signal sig) { LIBC.kill(0,SIGTERM); System.exit(-1); } }); // hang forever Object o = new Object(); synchronized (o) { o.wait(); } } /** * Creates a bound {@link ServerSocket} that will be shared by all worker processes. * This method is called in the frontend process. */ protected abstract ServerSocket createServerSocket() throws Exception; /** * Determines the Unix file descriptor number of the given {@link ServerSocket}. */ private int getUnixFileDescriptor(ServerSocket ss) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Field $impl = ss.getClass().getDeclaredField("impl"); $impl.setAccessible(true); SocketImpl socketImpl = (SocketImpl)$impl.get(ss); Method $getFileDescriptor = SocketImpl.class.getDeclaredMethod("getFileDescriptor"); $getFileDescriptor.setAccessible(true); FileDescriptor fd = (FileDescriptor) $getFileDescriptor.invoke(socketImpl); Field $fd = fd.getClass().getDeclaredField("fd"); $fd.setAccessible(true); return (Integer)$fd.get(fd); } protected void worker() throws Exception { String port = System.getProperty(NetworkServer.class.getName() + ".port"); worker(recreateServerSocket(Integer.parseInt(port))); } /** * Worker thread main code. * * @param ss * The server socket that the frontend process created. */ protected abstract void worker(ServerSocket ss) throws Exception; /** * Recreates a bound {@link ServerSocket} on the given file descriptor. */ private ServerSocket recreateServerSocket(int fdn) throws Exception { // create a properly populated FileDescriptor FileDescriptor fd = new FileDescriptor(); Field $fd = FileDescriptor.class.getDeclaredField("fd"); $fd.setAccessible(true); $fd.set(fd,fdn); // now create a PlainSocketImpl Class $PlainSocketImpl = Class.forName("java.net.PlainSocketImpl"); Constructor $init = $PlainSocketImpl.getDeclaredConstructor(FileDescriptor.class); $init.setAccessible(true); SocketImpl socketImpl = (SocketImpl)$init.newInstance(fd); // then wrap that into ServerSocket ServerSocket ss = new ServerSocket(); ss.bind(new InetSocketAddress(0)); Field $impl = ServerSocket.class.getDeclaredField("impl"); $impl.setAccessible(true); $impl.set(ss,socketImpl); return ss; } private static final Logger LOGGER = Logger.getLogger(NetworkServer.class.getName()); private static final int SIGTERM = 15; private static final String MODE_PROPERTY = NetworkServer.class.getName() + ".mode"; } kohsuke-akuma-1bf91fd/src/site/000077500000000000000000000000001172572472500165245ustar00rootroot00000000000000kohsuke-akuma-1bf91fd/src/site/apt/000077500000000000000000000000001172572472500173105ustar00rootroot00000000000000kohsuke-akuma-1bf91fd/src/site/apt/index.apt000066400000000000000000000107471172572472500211360ustar00rootroot00000000000000What is this? This is a Java library you can use in your application to support Unix daemonization. By taking advantages of POSIX API, this library lets you fork your process into background with proper daemonization steps. It currently has several features, which we'll see in more details. Daemonization One feature of this library lets you fork a copy into background as a real Unix daemon, when started in the foreground. While doing this, you can also tweak JVM parameters or arguments. Unlike the implementation based on <<>>, this version correctly starts a new process group, changes the current directory, and closes file descriptors. * How do I use it? Your integrate akuma into your main method like this: ------------------------------------------------ public static void main(String[] args) { Daemonizer d = new Daemonizer(); if(d.isDaemonized()) { // perform initialization as a daemon // this involves in closing file descriptors, recording PIDs, etc. d.init(); } else { // if you are already daemonized, no point in daemonizing yourself again, // so do this only when you aren't daemonizing. if(you decide to launch a copy into background) { d.daemonize(...); System.exit(0); } } // your normal main code follows // this part can be executed in two ways // 1) the user runs your process in the foreground // 2) you decided to daemonize yourself, in which case the newly forked daemon will execute this code, // while the originally executed foreground Java process exits before it gets here. ... } ------------------------------------------------ JVM Re-launch Another feature of this library lets you re-launch a JVM with different VM options, without forking a new child process. This allows applications to tweak parameters that can be only changed at the JVM start-up time, such as heap size, diagnostic options, etc. This overwrites the current process via POSIX <<>> call, so you'll retain the same stdin/stdout/stderr, and the same parent process. In comparison, Starting yourself with <<>> will create a child process with different stdin/stdout, and when the parent is killed, the child is left behind without a controlling terminal. * How do I use it? ------------------------------------------------ JavaVMArguments args = JavaVMArguments.current(); // tweak JVM launch options Daemon.selfExec(args); ------------------------------------------------ Multi-process network server This library also lets you write a multi-process network server that listens on the same TCP/IP port. By forking multiple processes, you improve the robustness of your server --- a single destroyed process will not interrupt the service as other worker processes will take over the processing. This is how Unix daemons have been traditionally written, such as Apache, yet it was impossible to do this in Java because it doesn't provide an API to let file descriptors to be inherited into children. * How do I use it? Your integrate akuma into your main method like this: ----------------------------------------------- public class EchoServer extends NetworkServer { public static void main(String[] args) throws Exception { new EchoServer(args).run(); } public EchoServer(String[] args) { super(args); } @Override protected void forkWorkers(JavaVMArguments args) throws Exception { // fork two worker processes forkWorkerThreads(args, 2); } @Override protected ServerSocket createServerSocket() throws Exception { // listen on port 12345 return new ServerSocket(12345); } @Override protected void worker(ServerSocket ss) throws Exception { byte[] buf = new byte[1024]; // run a simple echo server while(true) { Socket s = ss.accept(); ... serve this socket ... } } } ----------------------------------------------- {{{https://akuma.dev.java.net/nonav/xref/com/sun/akuma/EchoServer.html}The complete example}} is avalable separately. Compatibility Because of the difficulty in Java and POSIX API to obtain the OS-level arguments of the process, not all POSIX-compliant operating systems are supported. The supported platforms are: * Linux (x86,amd64) * Solaris (x86,amd64,sparc,sparcv9) * Mac OS X [] kohsuke-akuma-1bf91fd/src/site/site.xml000066400000000000000000000016261172572472500202170ustar00rootroot00000000000000 ${project.name} http://${project.artifactId}.kohsuke.org/ org.kohsuke maven-skin 1.2

${reports}