View Javadoc
1   /*
2    * The MIT License
3    *
4    * Copyright (c) 2009-, Sun Microsystems, Inc., CloudBees, 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.Memory;
27  import com.sun.jna.Native;
28  import com.sun.jna.NativeLong;
29  import com.sun.jna.StringArray;
30  import static com.sun.akuma.CLibrary.LIBC;
31  
32  import java.io.FileWriter;
33  import java.io.IOException;
34  import java.io.File;
35  import java.lang.reflect.Method;
36  import java.util.logging.Level;
37  import java.util.logging.Logger;
38  
39  /**
40   * Forks a copy of the current process into the background.
41   *
42   * <p>
43   * Because of the fork/exec involved in doing this, your code has to call Daemonizer in a certain sequence.
44   * Specifically, from your main method:
45   * <pre>
46   * public static void main(String[] args) {
47   *     Daemon d = new Daemon();
48   *     if(d.isDaemonized()) {
49   *         // perform initialization as a daemon
50   *         // this involves in closing file descriptors, recording PIDs, etc.
51   *         d.{@linkplain #init() init}();
52   *     } else {
53   *         // if you are already daemonized, no point in daemonizing yourself again,
54   *         // so do this only when you aren't daemonizing.
55   *         if(you decide to launch a copy into background) {
56   *             d.daemonize(...);
57   *             System.exit(0);
58   *         }
59   *     }
60   *
61   *     // your normal main code follows
62   *     // this part can be executed in two ways
63   *     // 1) the user runs your process in the foreground
64   *     // 2) you decided to daemonize yourself, in which case the newly forked daemon will execute this code,
65   *     //    while the originally executed foreground Java process exits before it gets here.
66   *     ...
67   * }
68   * </pre>
69   *
70   * <p>
71   * Alternatively, your main class can extend from Daemon, so that you can customize some of the behaviors.
72   *
73   * @author Kohsuke Kawaguchi
74   */
75  public class Daemon {
76      /**
77       * Do all the necessary steps in one go.
78       *
79       * @param daemonize
80       *      Parse the command line arguments and if the application should be
81       *      daemonized, pass in true.
82       */
83      public void all(boolean daemonize) throws Exception {
84          if(isDaemonized())
85              init();
86          else {
87              if(daemonize) {
88                  daemonize();
89                  System.exit(0);
90              }
91          }
92      }
93  
94      /**
95       * Returns true if the current process is already launched as a daemon
96       * via {@link #daemonize()}.
97       */
98      public boolean isDaemonized() {
99          return System.getProperty(Daemon.class.getName())!=null;
100     }
101 
102     /**
103      * Relaunches the JVM with the exact same arguments into the daemon.
104      */
105     public void daemonize() throws IOException {
106         daemonize(JavaVMArguments.current());
107     }
108 
109     /**
110      * Relaunches the JVM with the given arguments into the daemon.
111      */
112     public void daemonize(JavaVMArguments args) {
113         if(isDaemonized())
114             throw new IllegalStateException("Already running as a daemon");
115 
116         if (System.getProperty("com.sun.management.jmxremote.port") != null) {
117             try {
118                 Method m = Class.forName("sun.management.Agent").getDeclaredMethod("stopRemoteManagementAgent");
119                 m.setAccessible(true);
120                 m.invoke(null);
121             } catch (Exception x) {
122                 LOGGER.log(Level.SEVERE, "could not simulate jcmd $$ ManagementAgent.stop (JENKINS-14529)", x);
123             }
124         }
125 
126         // let the child process now that it's a daemon
127         args.setSystemProperty(Daemon.class.getName(),"daemonized");
128 
129         // prepare for a fork
130         String exe = getCurrentExecutable();
131         StringArray sa = args.toStringArray();
132 
133         int i = LIBC.fork();
134         if(i<0) {
135             LIBC.perror("initial fork failed");
136             System.exit(-1);
137         }
138         if(i==0) {
139             // with fork, we lose all the other critical threads, to exec to Java again
140             LIBC.execv(exe,sa);
141             System.err.println("exec failed");
142             LIBC.perror("initial exec failed");
143             System.exit(-1);
144         }
145 
146         // parent exits
147     }
148 
149     /**
150      * Overwrites the current process with a new Java VM with the given JVM arguments.
151      */
152     public static void selfExec(JavaVMArguments args) {
153         LIBC.execv(getCurrentExecutable(), args.toStringArray());
154     }
155 
156     /**
157      * Prepares the current process to act as a daemon.
158      * The daemon's PID is written to the file <code>/var/run/daemon.pid</code>.
159      */
160     public void init() throws Exception {
161         init("/var/run/daemon.pid");
162     }
163     
164     /**
165      * Prepares the current process to act as a daemon.
166      * @param pidFile the filename to which the daemon's PID is written; 
167      * or, <code>null</code> to skip writing a PID file.
168      */
169     @SuppressWarnings({"OctalInteger"})
170     public void init(String pidFile) throws Exception {
171         // start a new process session
172         LIBC.setsid();
173 
174         closeDescriptors();
175 
176         chdirToRoot();
177         if (pidFile != null) writePidFile(pidFile);
178     }
179 
180     /**
181      * Closes inherited file descriptors.
182      *
183      * <p>
184      * This method can be overridden to no-op in a subtype. Useful for debugging daemon processes
185      * when they don't work correctly.
186      */
187     protected void closeDescriptors() throws IOException {
188         if(!Boolean.getBoolean(Daemon.class.getName()+".keepDescriptors")) {
189             System.out.close();
190             System.err.close();
191             System.in.close();
192         }
193 
194         // ideally we'd like to close all other descriptors, but that would close
195         // jar files used as classpath, and break JVM.
196     }
197 
198     /**
199      * change directory to '/' to avoid locking directories.
200      */
201     protected void chdirToRoot() {
202         LIBC.chdir("/");
203         System.setProperty("user.dir","/");
204     }
205 
206     /**
207      * Writes out the PID of the current process to the specified file.
208      * @param pidFile the filename to write the PID to.
209      */
210     protected void writePidFile(String pidFile) throws IOException {
211         try {
212             FileWriter fw = new FileWriter(pidFile);
213             fw.write(String.valueOf(LIBC.getpid()));
214             fw.close();
215         } catch (IOException e) {
216             // if failed to write, keep going because maybe we are run from non-root
217         }
218     }
219 
220     /**
221      * Gets the current executable name.
222      */
223     public static String getCurrentExecutable() {
224         int pid = LIBC.getpid();
225         String name = "/proc/" + pid + "/exe";
226         File exe = new File(name);
227         if(exe.exists()) {
228             try {
229                 String path = resolveSymlink(exe);
230                 if (path!=null)     return path;
231             } catch (IOException e) {
232                 LOGGER.log(Level.FINE,"Failed to resolve symlink "+exe,e);
233             }
234             return name;
235         }
236 
237         // cross-platform fallback
238         return System.getProperty("java.home")+"/bin/java";
239     }
240 
241     private static String resolveSymlink(File link) throws IOException {
242         String filename = link.getAbsolutePath();
243 
244         for (int sz=512; sz < 65536; sz*=2) {
245             Memory m = new Memory(sz);
246             int r = LIBC.readlink(filename,m,new NativeLong(sz));
247             if (r<0) {
248                 int err = Native.getLastError();
249                 if (err==22/*EINVAL --- but is this really portable?*/)
250                     return null; // this means it's not a symlink
251                 throw new IOException("Failed to readlink "+link+" error="+ err+" "+ LIBC.strerror(err));
252             }
253 
254             if (r==sz)
255                 continue;   // buffer too small
256 
257             byte[] buf = new byte[r];
258             m.read(0,buf,0,r);
259             return new String(buf);
260         }
261 
262         throw new IOException("Failed to readlink "+link);
263     }
264 
265     /**
266      * Flavor of {@link Daemon} that doesn't change the current directory.
267      *
268      * <p>
269      * This turns out to be often more useful as JavaVM can take lot of arguments and system properties
270      * that use paths, and when we CD they won't work.
271      */
272     public static class WithoutChdir extends Daemon {
273         @Override
274         protected void chdirToRoot() {
275             // noop
276         }
277     }
278 
279     private static final Logger LOGGER = Logger.getLogger(Daemon.class.getName());
280 }