/*
* 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.gvmax.assembly.daemon;
import static com.gvmax.assembly.daemon.CLibrary.LIBC;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.gvmax.common.util.IOUtil;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.StringArray;
public class Daemon {
private String pidFile;
private int debugPort = -1;
private static final Logger LOGGER = Logger.getLogger(Daemon.class.getName());
public Daemon(String pidFile, int debugPort) {
this.pidFile = pidFile;
this.debugPort = debugPort;
}
public void startDaemon() throws IOException {
startDaemon(JavaVMArguments.current());
}
public void startDaemon(JavaVMArguments args) {
if (isDaemonized()) {
throw new IllegalStateException("Already running as a daemon");
}
args.setSystemProperty(Daemon.class.getName(), "daemonized");
if (debugPort != -1) {
args.add(1, "-agentlib:jdwp=transport=dt_socket,address=" + debugPort + ",server=y,suspend=n");
}
// 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
}
public boolean isDaemonized() {
return System.getProperty(Daemon.class.getName()) != null;
}
public boolean isRunning() {
return getDaemonPid() != null;
}
public boolean stopDaemon() {
Integer pid = getDaemonPid();
if (pid != null) {
LIBC.kill(pid, 2);
return true;
}
return false;
}
public void restartDaemon() throws IOException {
stopDaemon();
startDaemon();
}
public void initDaemon() throws Exception {
LIBC.setsid();
closeDescriptors();
if (pidFile != null) {
writePidFile();
}
}
protected void closeDescriptors() throws IOException {
if (!Boolean.getBoolean(Daemon.class.getName() + ".keepDescriptors")) {
System.out.close();
System.err.close();
System.in.close();
}
}
public Integer getDaemonPid() {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(pidFile));
int pid = Integer.parseInt(reader.readLine());
return pid;
} catch (Exception e) {
return null;
} finally {
IOUtil.close(reader);
}
}
protected void writePidFile() throws IOException {
FileWriter fw = null;
try {
File file = new File(pidFile);
file.deleteOnExit();
fw = new FileWriter(file);
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
e.printStackTrace();
} finally {
IOUtil.close(fw);
}
}
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);
}
}