All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.onebusaway.cli.Daemonizer Maven / Gradle / Ivy

/**
 * Copyright (C) 2012 Google, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.onebusaway.cli;

import static com.sun.akuma.CLibrary.LIBC;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Properties;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;

import com.sun.akuma.Daemon;
import com.sun.akuma.JavaVMArguments;
import com.sun.jna.StringArray;

/**
 * Supports portable daemonization of a Java process. Uses the Sakuma wrapper of
 * LIBC to perform daemonization, so will only work on systems that have LIBC.
 * Works with the {@code commons-cli} command-line-interface argument parsing to
 * define and process command line arguments defining daemonization parameters
 * like a pid file, jvm args, and process output log files.
 * 
 * See {@link #buildOptions(Options)} and
 * {@link #handleDaemonization(CommandLine)} for convenience methods to perform
 * daemonization.
 * 
 * @author bdferris
 */
public class Daemonizer {

  private static final String ARG_DAEMONIZE = "daemonize";

  private static final String ARG_PID_FILE = "pidFile";

  private static final String ARG_JVM_ARGS = "jvmArgs";

  private static final String ARG_ERROR_FILE = "errorFile";

  private static final String ARG_OUTPUT_FILE = "outputFile";

  private static final String ARG_EXE = "exe";

  private static MyLog _log = new MyLog();

  private File _outputFile;

  private File _errorFile;

  private File _pidFile;

  private File _workingDirectory;

  private String _exe;

  private Collection _jvmArgs;

  /**
   * Add common daemonization command line arguments to a {@code commons-cli}
   * {@link Options} collection.
   * 
   * @param options we add command line options to this target options
   *          collection
   * @return the same target options collection
   */
  public static Options buildOptions(Options options) {
    options.addOption(ARG_JVM_ARGS, true, "custom jvm args for the daemon");
    options.addOption(ARG_OUTPUT_FILE, true,
        "stdout output (&2 to redirect to stderr)");
    options.addOption(ARG_ERROR_FILE, true,
        "stderr output (&1 to redirect to stdout)");
    options.addOption(ARG_PID_FILE, true, "pid file");
    options.addOption(ARG_EXE, true, "specify exe path");
    options.addOption(ARG_DAEMONIZE, false, "run as a daemon");

    return options;
  }

  /**
   * Convenience method to handle daemonization of a command line Java program
   * 
   * @param cli parsed command line option values
   * @return true if daemonization was performed
   */
  public static boolean handleDaemonization(CommandLine cli) throws Exception {

    if (cli.hasOption(ARG_DAEMONIZE)) {

      _log.debug("-daemonize option specified");

      Daemonizer daemonizer = new Daemonizer();

      if (cli.hasOption(ARG_OUTPUT_FILE))
        daemonizer.setOutputFile(new File(cli.getOptionValue(ARG_OUTPUT_FILE)));

      if (cli.hasOption(ARG_ERROR_FILE))
        daemonizer.setErrorFile(new File(cli.getOptionValue(ARG_ERROR_FILE)));

      if (cli.hasOption(ARG_PID_FILE))
        daemonizer.setPidFile(new File(cli.getOptionValue(ARG_PID_FILE)));

      if (cli.hasOption(ARG_JVM_ARGS)) {
        String[] jvmArgs = cli.getOptionValue(ARG_JVM_ARGS).split(" ");
        daemonizer.setJvmArgs(Arrays.asList(jvmArgs));
      }

      if (cli.hasOption(ARG_EXE)) {
        daemonizer.setExe(cli.getOptionValue(ARG_EXE));
      }

      daemonizer.daemonize();
      return true;
    }

    return false;
  }

  public void setOutputFile(File outputFile) {
    _outputFile = outputFile;
  }

  public void setErrorFile(File errorFile) {
    _errorFile = errorFile;
  }

  public void setPidFile(File pidFile) {
    _pidFile = pidFile;
  }

  public void setWorkingDirectory(File workingDirectory) {
    _workingDirectory = workingDirectory;
  }

  public void setJvmArgs(Collection jvmArgs) {
    _jvmArgs = jvmArgs;
  }

  private void setExe(String exe) {
    _exe = exe;
  }

  public void daemonize() throws Exception {

    DaemonImpl d = new DaemonImpl();

    if (d.isDaemonized()) {
      if (_log.isDebugEnabled()) {
        _log.debug("process is already daemonized");
        _log.debug("currentExecutabe=" + Daemon.getCurrentExecutable());
        Properties props = System.getProperties();
        for (Object key : props.keySet()) {
          Object value = props.get(key);
          _log.debug(key + "," + value);
        }
      }

      _log.debug("pre complete");
      LIBC.umask(0027);
      _log.debug("umask complete");

      if (_pidFile == null)
        d.init(null);
      else
        d.init(_pidFile.getAbsolutePath());

      _log.debug("init complete");

    } else {

      _log.debug("forking daemon process");

      JavaVMArguments arguments = JavaVMArguments.current();

      if (_jvmArgs != null)
        arguments.addAll(1, _jvmArgs);

      if (_log.isDebugEnabled()) {
        _log.debug("jvm args:");
        for (int i = 0; i < arguments.size(); i++)
          _log.debug(arguments.get(i));

        _log.debug("currentExecutabe=" + Daemon.getCurrentExecutable());
      }

      // This effectively performs a fork...
      d.daemonize(arguments);
      System.exit(0);
    }
  }

  /****
   * Private Methods
   ****/

  private boolean isRedirectOutputToError() {
    if (_outputFile == null || _errorFile == null)
      return false;
    return _outputFile.getName().equals("&2");
  }

  private boolean isRedirectErrorToOutput() {
    if (_outputFile == null || _errorFile == null)
      return false;
    return _errorFile.getName().equals("&1");
  }

  private class DaemonImpl extends Daemon {

    @Override
    protected void chdirToRoot() {
      super.chdirToRoot();
      if (_workingDirectory != null) {
        LIBC.chdir(_workingDirectory.getAbsolutePath());
        System.setProperty("user.dir", _workingDirectory.getAbsolutePath());
      }
    }

    @Override
    protected void closeDescriptors() throws IOException {

      boolean redirectOutputToError = isRedirectOutputToError();
      boolean redirectErrorToOutput = isRedirectErrorToOutput();

      if (redirectOutputToError && redirectErrorToOutput) {
        throw new IllegalStateException(
            "circular redirection amongst output and error");
      }

      super.closeDescriptors();

      if (redirectErrorToOutput) {
        PrintStream stream = new PrintStream(new FileOutputStream(_outputFile, true));
        System.setOut(stream);
        System.setErr(stream);
      } else if (redirectOutputToError) {
        PrintStream stream = new PrintStream(new FileOutputStream(_errorFile, true));
        System.setOut(stream);
        System.setErr(stream);
      } else {
        if (_outputFile != null)
          System.setOut(new PrintStream(new FileOutputStream(_outputFile, true)));
        if (_errorFile != null)
          System.setErr(new PrintStream(new FileOutputStream(_errorFile, true)));
      }
    }

    /**
     * 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();
      if (_exe != null)
        exe = _exe;

      StringArray sa = new StringArray(args.toArray(new String[args.size()]));

      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
    }
  }

  private static class MyLog {

    public boolean isDebugEnabled() {
      return true;
    }

    public void debug(String string) {
      try {
        PrintWriter out = new PrintWriter(new FileWriter("/tmp/log.out", true));
        out.println(string);
        out.flush();
        out.close();
      } catch (IOException ex) {
        throw new IllegalStateException(ex);
      }
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy