Package org.skife.gressil

Source Code of org.skife.gressil.Daemon

package org.skife.gressil;

import jnr.ffi.Library;
import jnr.ffi.Pointer;
import jnr.ffi.byref.IntByReference;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static java.lang.String.format;
import static java.util.Arrays.asList;

public class Daemon
{
    private final List<String> programArgs;
    private final File pidfile;
    private final File out;
    private final File err;
    private final List<String> extraVmArgs;
    private final List<String> extraProgramArgs;

    private static final MicroC posix = Library.loadLibrary("c", MicroC.class);

    public Daemon()
    {
        this(null,
             null,
             new File("/dev/null"),
             new File("/dev/null"),
             Collections.<String>emptyList(),
             Collections.<String>emptyList());
    }

    private Daemon(List<String> argv, File pidfile, File out, File err, List<String> extraVmArgs, List<String> extraProgramArgs)
    {
        this.programArgs = argv;
        this.pidfile = pidfile;
        this.out = out;
        this.err = err;
        this.extraVmArgs = extraVmArgs;
        this.extraProgramArgs = extraProgramArgs;
    }

    /**
     * tl;dr pass the String[] received in your public static void main(String[] args) call here.
     * <p/>
     * This is the preferred way to get the program arguments. The argument here should be the same
     * array of strings as was passed to main(String[] args).
     * <p/>
     * If the args are *not* passed here, we will attempt to figure out what they are by poking
     * around the JVM, but the "poke around the JVM" method gets confused by whitespace in argument
     * names (ie, quoted arguments) which in the real ARGV will be one element, but via the poke around
     * the JVM method of figuring out program args the whitespace will lead to it being two arguments.
     * This usually leads to undesired behavior.
     */
    public Daemon withMainArgs(String... args)
    {
        return withArgv(asList(args));
    }

    public Daemon withArgv(List<String> args)
    {
        return new Daemon(args, pidfile, out, err, extraVmArgs, extraProgramArgs);
    }

    public Daemon withExtraJvmArgs(List<String> extraVmArgs)
    {
        return new Daemon(programArgs, pidfile, out, err, extraVmArgs, extraProgramArgs);
    }

    public Daemon withExtraJvmArgs(String... extraVmArgs)
    {
        return new Daemon(programArgs, pidfile, out, err, asList(extraVmArgs), extraProgramArgs);
    }

    public Daemon withExtraMainArgs(List<String> extraProgramArgs)
    {
        return new Daemon(programArgs, pidfile, out, err, extraVmArgs, extraProgramArgs);
    }

    public Daemon withExtraMainArgs(String... extraProgramArgs)
    {
        return new Daemon(programArgs, pidfile, out, err, extraVmArgs, asList(extraProgramArgs));
    }

    public Daemon withPidFile(File pidfile)
    {
        return new Daemon(programArgs, pidfile, out, err, extraVmArgs, extraProgramArgs);
    }

    public Daemon withStdout(File out)
    {
        return new Daemon(programArgs, pidfile, out, err, extraVmArgs, extraProgramArgs);
    }

    public Daemon withStderr(File err)
    {
        return new Daemon(programArgs, pidfile, out, err, extraVmArgs, extraProgramArgs);
    }

    Status forkish() throws IOException
    {
        if (isDaemon()) {
            posix.setsid();

            OutputStream old_out = System.out;
            OutputStream old_err = System.err;

            System.setOut(new PrintStream(new FileOutputStream(out, true)));
            System.setErr(new PrintStream(new FileOutputStream(err, true)));
            old_err.close();
            old_out.close();

            if (pidfile != null) {
                FileOutputStream p_out = new FileOutputStream(pidfile);
                p_out.write(String.valueOf(posix.getpid()).getBytes());
                p_out.close();
                pidfile.deleteOnExit();
            }

            return Status.child(posix.getpid());
        }
        else
        {
            String[] envp = getEnv(Daemon.class.getName() + "=daemon");
            List<String> argv = buildARGV(posix);

            jnr.ffi.Runtime runtime = jnr.ffi.Runtime.getSystemRuntime();
            Pointer NULL = Pointer.wrap(runtime, 0L);
            IntByReference child_pid = new IntByReference();

            int rs = posix.posix_spawnp(child_pid, argv.get(0), NULL, NULL,
                                        argv.toArray(new String[argv.size()]), envp);
            if (rs != 0) {
                throw new RuntimeException(posix.strerror(rs));
            }
            return Status.parent(child_pid.getValue());
        }
    }

    public void daemonize() throws IOException
    {
        if (forkish().isParent()) {
            System.exit(0);
        }
    }

    public static boolean isDaemon()
    {
        return "daemon".equals(System.getenv(Daemon.class.getName()));
    }

    public List<String> buildARGV(MicroC posix)
    {
        List<String> argv;
        String os = System.getProperty("os.name");
        if (this.programArgs != null) {
            // if we had args passed to us, don't mess around, just use them
            argv = new JvmBasedArgvFinder(this.programArgs).getArgv();
        }
        else if ("Linux".equals(os)) {
            argv = new LinuxArgvFinder(posix.getpid()).getArgv();
        }
//        else if ("Mac OS X".equals(os)) {
//            argv = new MacARGVFinder().getArgv();
//            if (!argv.get(0).endsWith("java")) {
//                this works sometimes, not others, needs debugging. For now this heuristic seems to work
//                argv = new JvmBasedArgvFinder(this.programArgs).getArgv();
//            }
//        }
        else
        {
            argv = new JvmBasedArgvFinder(this.programArgs).getArgv();
        }

        if (this.extraVmArgs.size() > 0) {
            List<String> new_argv = new ArrayList<String>(argv.size() + extraVmArgs.size());
            new_argv.add(argv.get(0));
            new_argv.addAll(extraVmArgs);
            new_argv.addAll(argv.subList(1, argv.size()));
            argv = new_argv;
        }

        if (this.extraProgramArgs.size() > 0) {
            argv.addAll(extraProgramArgs);
        }

        return argv;
    }

    /**
     * Creates vm arguments for jdwp remote debugging, suspending the VM on startup
     *
     * @param port port to listen for remote debugger on
     */
    public static List<String> waitForRemoteDebugOnPort(int port)
    {
        return asList("-Xdebug", format("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=%d", port));
    }

    /**
     * Creates vm arguments for jdwp remote debugging, without suspending the VM on startup
     *
     * @param port port to listen for remote debugger on
     */
    public static List<String> remoteDebugOnPort(int port)
    {
        return asList("-Xdebug", format("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=%d", port));
    }

    public static String[] getEnv(String... additions)
    {
        String[] envp = new String[System.getenv().size() + additions.length];
        int i = 0;
        for (Map.Entry<String, String> pair : System.getenv().entrySet()) {
            envp[i++] = new StringBuilder(pair.getKey()).append("=").append(pair.getValue()).toString();
        }
        System.arraycopy(additions, 0, envp, envp.length - 1, additions.length);
        return envp;
    }

    /**
     * 0  program is running or service is OK
     * 1  program is dead and /var/run pid file exists
     * 2  program is dead and /var/lock lock file exists
     * 3  program is not running
     * 4  program or service status is unknown
     * 5-99  reserved for future LSB use
     * 100-149  reserved for distribution use
     * 150-199  reserved for application use
     * 200-254  reserved
     */
    public DaemonStatus checkStatus()
    {
        if (this.pidfile == null) {
            throw new IllegalStateException("No pidfile specified, cannot check status!");
        }
        if (!pidfile.exists()) {
            return DaemonStatus.STATUS_NOT_RUNNING;
        }

        final int pid;
        try
        {
            byte[] content = Files.readAllBytes(pidfile.toPath());
            String s = new String(content, StandardCharsets.UTF_8).trim();
            pid = Integer.parseInt(s);

        }
        catch (Exception e)
        {
            System.err.println(e.getMessage());
            return DaemonStatus.STATUS_UNKNOWN;
        }

        int rs = posix.kill(pid, 0);
        if (rs == 0) {
            return DaemonStatus.STATUS_RUNNING;
        }
        else
        {
            return DaemonStatus.STATUS_DEAD;
        }
    }

    public DaemonStatus stop()
    {
        /*
         1  generic or unspecified error (current practice)
         2  invalid or excess argument(s)
         3  unimplemented feature (for example, "reload")
         4  user had insufficient privilege
         5  program is not installed
         6  program is not configured
         7  program is not running
         8-99  reserved for future LSB use
         100-149  reserved for distribution use
         150-199  reserved for application use
         200-254  reserved
         */
        if (this.pidfile == null) {
            throw new IllegalStateException("No pidfile specified, cannot stop!");
        }
        if (!pidfile.exists()) {
            return DaemonStatus.STOP_NOT_RUNNING;
        }

        final int pid;
        try
        {
            byte[] content = Files.readAllBytes(pidfile.toPath());
            String s = new String(content, StandardCharsets.UTF_8).trim();
            pid = Integer.parseInt(s);

        }
        catch (Exception e)
        {
            System.err.println(e.getMessage());
            return DaemonStatus.STOP_GENERAL_ERROR;
        }

        int rs = posix.kill(pid, 2);
        if (rs == 0) {
            return DaemonStatus.STOP_SUCCESS;
        }
        else
        {
            return DaemonStatus.STOP_GENERAL_ERROR;
        }
    }

    public void execute(DaemonCommand cmd) throws IOException
    {
        final DaemonStatus status;
        switch (cmd) {
            case start:
                daemonize();
                break;
            case status:
                status = checkStatus();
                System.exit(status.getExitCode());
                break;
            case stop:
                status = stop();
                System.exit(status.getExitCode());
                break;
        }
    }
}
TOP

Related Classes of org.skife.gressil.Daemon

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.