| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 |
| * |
| * https://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. |
| */ |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.io.UnsupportedEncodingException; |
| import java.net.ServerSocket; |
| import java.net.Socket; |
| import java.nio.charset.StandardCharsets; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.Vector; |
| |
| import org.apache.commons.daemon.Daemon; |
| import org.apache.commons.daemon.DaemonContext; |
| import org.apache.commons.daemon.DaemonController; |
| import org.apache.commons.daemon.DaemonInitException; |
| |
| public class SimpleDaemon implements Daemon, Runnable { |
| |
| private ServerSocket server; |
| private Thread thread; |
| private DaemonController controller; |
| private volatile boolean stopping; |
| private String directory; |
| private final Vector<Handler> handlers; |
| |
| public static native void toto(); |
| |
| public SimpleDaemon() { |
| super(); |
| System.err.println("SimpleDaemon: instance "+this.hashCode()+ |
| " created"); |
| this.handlers = new Vector<>(); |
| } |
| |
| @Override |
| protected void finalize() { |
| System.err.println("SimpleDaemon: instance "+this.hashCode()+ |
| " garbage collected"); |
| } |
| |
| /** |
| * init and destroy were added in jakarta-tomcat-daemon. |
| */ |
| @Override |
| public void init(DaemonContext context) |
| throws Exception { |
| System.err.println("SimpleDaemon: instance " + this.hashCode() + " init"); |
| |
| int port = 1200; |
| |
| String[] a = context.getArguments(); |
| try { |
| if (a.length > 0) |
| port = Integer.parseInt(a[0]); |
| } catch (NumberFormatException ex) { |
| throw new DaemonInitException("parsing port", ex); |
| } |
| if (a.length > 1) |
| this.directory = a[1]; |
| else |
| this.directory = "/tmp"; |
| |
| // Dump a message |
| System.err.println("SimpleDaemon: loading on port " + port); |
| |
| // Set up this simple daemon |
| this.controller = context.getController(); |
| this.server = new ServerSocket(port); |
| this.thread = new Thread(this); |
| } |
| |
| @Override |
| public void start() { |
| // Dump a message |
| System.err.println("SimpleDaemon: starting"); |
| |
| // Start |
| this.thread.start(); |
| } |
| |
| @Override |
| public void stop() |
| throws IOException, InterruptedException { |
| // Dump a message |
| System.err.println("SimpleDaemon: stopping"); |
| |
| // Close the ServerSocket. This will make our thread to terminate |
| this.stopping = true; |
| this.server.close(); |
| |
| // Wait for the main thread to exit and dump a message |
| this.thread.join(5000); |
| System.err.println("SimpleDaemon: stopped"); |
| } |
| |
| @Override |
| public void destroy() { |
| System.err.println("SimpleDaemon: instance "+this.hashCode()+ |
| " destroy"); |
| } |
| |
| @Override |
| public void run() { |
| int number=0; |
| |
| System.err.println("SimpleDaemon: started acceptor loop"); |
| try { |
| while (!this.stopping) { |
| Socket socket = this.server.accept(); |
| Handler handler = new Handler(socket, this, this.controller); |
| handler.setConnectionNumber(number++); |
| handler.setDirectoryName(this.directory); |
| new Thread(handler).start(); |
| } |
| } catch (IOException e) { |
| // Don't dump any error message if we are stopping. A IOException |
| // is generated when the ServerSocket is closed in stop() |
| if (!this.stopping) e.printStackTrace(System.err); |
| } |
| |
| // Terminate all handlers that at this point are still open |
| Enumeration<Handler> openhandlers = this.handlers.elements(); |
| while (openhandlers.hasMoreElements()) { |
| Handler handler = openhandlers.nextElement(); |
| System.err.println("SimpleDaemon: dropping connection "+ |
| handler.getConnectionNumber()); |
| handler.close(); |
| } |
| |
| System.err.println("SimpleDaemon: exiting acceptor loop"); |
| } |
| |
| protected void addHandler(Handler handler) { |
| synchronized (handler) { |
| this.handlers.add(handler); |
| } |
| } |
| |
| protected void removeHandler(Handler handler) { |
| synchronized (handler) { |
| this.handlers.remove(handler); |
| } |
| } |
| |
| public static class Handler implements Runnable { |
| |
| private final DaemonController controller; |
| private final SimpleDaemon parent; |
| private String directory; // Only set before thread is started |
| private final Socket socket; |
| private int number; // Only set before thread is started |
| |
| public Handler(Socket s, SimpleDaemon p, DaemonController c) { |
| super(); |
| this.socket = s; |
| this.parent = p; |
| this.controller = c; |
| } |
| |
| @Override |
| public void run() { |
| this.parent.addHandler(this); |
| System.err.println("SimpleDaemon: connection " + this.number + " opened from " + this.socket.getInetAddress()); |
| try { |
| InputStream in = this.socket.getInputStream(); |
| OutputStream out = this.socket.getOutputStream(); |
| handle(in, out); |
| this.socket.close(); |
| } catch (IOException e) { |
| e.printStackTrace(System.err); |
| } |
| System.err.println("SimpleDaemon: connection " + this.number + " closed"); |
| this.parent.removeHandler(this); |
| } |
| |
| public void close() { |
| try { |
| this.socket.close(); |
| } catch (IOException e) { |
| e.printStackTrace(System.err); |
| } |
| } |
| |
| public void setConnectionNumber(int number) { |
| this.number=number; |
| } |
| |
| public int getConnectionNumber() { |
| return(this.number); |
| } |
| |
| public void setDirectoryName(String directory) { |
| this.directory = directory; |
| } |
| |
| public String getDirectoryName() { |
| return(this.directory); |
| } |
| |
| public void createFile(String name) throws IOException { |
| OutputStream file = new FileOutputStream(name, true); |
| PrintStream out = new PrintStream(file); |
| SimpleDateFormat fmt = new SimpleDateFormat(); |
| |
| out.println(fmt.format(new Date())); |
| out.close(); |
| file.close(); |
| } |
| |
| public void createDir(String name) |
| throws IOException { |
| File file = new File(name); |
| boolean ok = file.mkdirs(); |
| if (!ok) |
| throw new IOException("mkdirs for "+name+" failed"); |
| createFile(name); |
| } |
| |
| public void handle(InputStream in, OutputStream os) { |
| PrintStream out = null; |
| try { |
| out = new PrintStream(os, true, StandardCharsets.US_ASCII.name()); |
| } catch (UnsupportedEncodingException ex) { |
| out = new PrintStream(os, true); |
| } |
| |
| while(true) { |
| try { |
| // If we don't have data in the System InputStream, we want |
| // to ask to the user for an option. |
| if (in.available() == 0) { |
| out.println(); |
| out.println("Please select one of the following:"); |
| out.println(" 1) Shutdown"); |
| out.println(" 2) Reload"); |
| out.println(" 3) Create a file"); |
| out.println(" 4) Disconnect"); |
| out.println(" 5) Cause a core of the JVM"); |
| out.println(" 6) Create a directory"); |
| out.print("Your choice: "); |
| } |
| |
| // Read an option from the client |
| int x = in.read(); |
| |
| switch (x) { |
| // If the socket was closed, we simply return |
| case -1: |
| return; |
| |
| // Attempt to shutdown |
| case '1': |
| out.println("Attempting a shutdown..."); |
| try { |
| this.controller.shutdown(); |
| } catch (IllegalStateException e) { |
| out.println(); |
| out.println("Can't shutdown now"); |
| e.printStackTrace(out); |
| } |
| break; |
| |
| // Attempt to reload |
| case '2': |
| out.println("Attempting a reload..."); |
| try { |
| this.controller.reload(); |
| } catch (IllegalStateException e) { |
| out.println(); |
| out.println("Can't reload now"); |
| e.printStackTrace(out); |
| } |
| break; |
| |
| // Create a file |
| case '3': |
| String name=this.getDirectoryName()+ |
| "/SimpleDaemon."+ |
| this.getConnectionNumber()+ |
| ".tmp"; |
| try { |
| this.createFile(name); |
| out.println("File '"+name+"' created"); |
| } catch (IOException e) { |
| e.printStackTrace(out); |
| } |
| break; |
| |
| // Disconnect |
| case '4': |
| out.println("Disconnecting..."); |
| return; |
| |
| // Crash JVM in a native call: It need an so file ;-) |
| case '5': |
| System.load(System.getProperty("native.library", "./Native.so")); |
| toto(); |
| break; |
| |
| // Create a directory (PR 30177 with 1.4.x and 1.5.0 |
| case '6': |
| String name1=this.getDirectoryName()+ |
| "/a/b/c/d/e"+ |
| "/SimpleDaemon."+ |
| this.getConnectionNumber()+ |
| ".tmp"; |
| try { |
| this.createDir(name1); |
| out.println("File '"+name1+"' created"); |
| } catch (IOException e) { |
| e.printStackTrace(out); |
| } |
| break; |
| |
| // Discard any carriage return / newline characters |
| case '\r': |
| case '\n': |
| break; |
| |
| // We got something that we weren't supposed to get |
| default: |
| out.println("Unknown option '"+(char)x+"'"); |
| break; |
| |
| } |
| |
| // If we get an IOException we return (disconnect) |
| } catch (IOException e) { |
| System.err.println("SimpleDaemon: IOException in "+ |
| "connection "+ |
| this.getConnectionNumber()); |
| return; |
| } |
| } |
| } |
| } |
| } |