Merge tag 'v2.3.28' into testing
[lgpl/argeo-commons.git] / org.argeo.init / src / org / argeo / init / SysInitMain.java
diff --git a/org.argeo.init/src/org/argeo/init/SysInitMain.java b/org.argeo.init/src/org/argeo/init/SysInitMain.java
new file mode 100644 (file)
index 0000000..8e59c24
--- /dev/null
@@ -0,0 +1,308 @@
+package org.argeo.init;
+//#! /usr/bin/java --source 17 @/usr/local/etc/freed/pid1/jvm.args
+
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.INFO;
+import static java.lang.System.Logger.Level.WARNING;
+
+import java.io.Console;
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import sun.misc.Signal;
+
+/** A minimalistic Linux init process. */
+class SysInitMain {
+       final static AtomicInteger runLevel = new AtomicInteger(-1);
+
+       private final static Logger logger = System.getLogger(SysInitMain.class.getName());
+
+       private final static List<String> initDServices = Collections.synchronizedList(new ArrayList<>());
+
+       public static void main(String... args) {
+               try {
+                       final long pid = ProcessHandle.current().pid();
+                       Signal.handle(new Signal("TERM"), (signal) -> {
+                               System.out.println("SIGTERM caught");
+                               System.exit(0);
+                       });
+                       Signal.handle(new Signal("INT"), (signal) -> {
+                               System.out.println("SIGINT caught");
+                               System.exit(0);
+                       });
+                       Signal.handle(new Signal("HUP"), (signal) -> {
+                               System.out.println("SIGHUP caught");
+                               System.exit(0);
+                       });
+
+                       boolean isSystemInit = pid == 1 || pid == 2;
+
+                       if (isSystemInit && args.length > 0 && ("1".equals(args[0]) //
+                                       || "single".equals(args[0]) //
+                                       || "emergency".equals(args[0]))) {
+                               runLevel.set(1);
+                               for (Object key : new TreeMap<>(System.getProperties()).keySet()) {
+                                       System.out.println(key + "=" + System.getProperty(key.toString()));
+                               }
+                               System.out.println("Single user mode");
+                               System.out.flush();
+                               ProcessBuilder pb = new ProcessBuilder("/bin/bash");
+                               pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+                               pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
+                               pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
+                               Process singleUserShell = pb.start();
+                               singleUserShell.waitFor();
+                       } else {
+                               if (args.length == 0)
+                                       runLevel.set(5);
+                               else
+                                       runLevel.set(Integer.parseInt(args[0]));
+
+                               if (runLevel.get() == 0) {// shutting down the whole system
+                                       if (!isSystemInit) {
+                                               logger.log(INFO, "Shutting down system...");
+                                               shutdown(false);
+                                               System.exit(0);
+                                       } else {
+                                               logger.log(ERROR, "Cannot start at run level " + runLevel.get());
+                                               System.exit(1);
+                                       }
+                               } else if (runLevel.get() == 6) {// reboot the whole system
+                                       if (!isSystemInit) {
+                                               logger.log(INFO, "Rebooting the system...");
+                                               shutdown(true);
+                                       } else {
+                                               logger.log(ERROR, "Cannot start at run level " + runLevel.get());
+                                               System.exit(1);
+                                       }
+                               }
+
+                               logger.log(INFO, "FREEd Init daemon starting with pid " + pid + " after "
+                                               + ManagementFactory.getRuntimeMXBean().getUptime() + " ms");
+                               // hostname
+                               String hostname = Files.readString(Paths.get("/etc/hostname"));
+                               new ProcessBuilder("/usr/bin/hostname", hostname).start();
+                               logger.log(DEBUG, "Set hostname to " + hostname);
+                               // networking
+                               initSysctl();
+                               startInitDService("networking", true);
+//                             Thread.sleep(3000);// leave some time for network to start up
+                               if (!waitForNetwork(10 * 1000))
+                                       logger.log(ERROR, "No network available");
+
+                               // OpenSSH
+                               // TODO make it coherent with Java sshd
+                               startInitDService("ssh", true);
+
+                               // NSS services
+                               startInitDService("nslcd", false);// Note: nslcd fails to stop
+
+                               // login prompt
+                               ServiceMain.addPostStart(() -> new LoginThread().start());
+
+                               // init Argeo CMS
+                               logger.log(INFO, "FREEd Init daemon starting Argeo Init after "
+                                               + ManagementFactory.getRuntimeMXBean().getUptime() + " ms");
+                               ServiceMain.main(args);
+                       }
+               } catch (Throwable e) {
+                       logger.log(ERROR, "Unexpected exception in free-pid1 init, shutting down... ", e);
+                       System.exit(1);
+               } finally {
+                       stopInitDServices();
+               }
+       }
+
+       static void initSysctl() {
+               try {
+                       Path sysctlD = Paths.get("/etc/sysctl.d/");
+                       for (Path conf : Files.newDirectoryStream(sysctlD, "*.conf")) {
+                               try {
+                                       new ProcessBuilder("/usr/sbin/sysctl", "-p", conf.toString()).start();
+                               } catch (IOException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+               } catch (IOException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       static void startInitDService(String serviceName, boolean stopOnShutdown) {
+               Path serviceInit = Paths.get("/etc/init.d/", serviceName);
+               if (Files.exists(serviceInit))
+                       try {
+                               int exitCode = new ProcessBuilder(serviceInit.toString(), "start").start().waitFor();
+                               if (exitCode != 0)
+                                       logger.log(ERROR, "Service " + serviceName + " dit not stop properly");
+                               else
+                                       logger.log(DEBUG, "Service " + serviceName + " started");
+                               if (stopOnShutdown)
+                                       initDServices.add(serviceName);
+//                                     Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+//                                             try {
+//                                                     new ProcessBuilder(serviceInit.toString(), "stop").start().waitFor();
+//                                             } catch (IOException | InterruptedException e) {
+//                                                     e.printStackTrace();
+//                                             }
+//                                     }, "FREEd stop service " + serviceName));
+                       } catch (IOException | InterruptedException e) {
+                               e.printStackTrace();
+                       }
+               else
+                       logger.log(WARNING, "Service " + serviceName + " not found and therefore not started");
+       }
+
+       static boolean waitForNetwork(long timeout) {
+               long begin = System.currentTimeMillis();
+               long duration = 0;
+               boolean networkAvailable = false;
+               try {
+                       networkAvailable: while (!networkAvailable) {
+                               duration = System.currentTimeMillis() - begin;
+                               if (duration > timeout)
+                                       break networkAvailable;
+                               Enumeration<NetworkInterface> netInterfaces = null;
+                               try {
+                                       netInterfaces = NetworkInterface.getNetworkInterfaces();
+                               } catch (SocketException e) {
+                                       throw new IllegalStateException("Cannot list network interfaces", e);
+                               }
+                               if (netInterfaces != null) {
+                                       while (netInterfaces.hasMoreElements()) {
+                                               NetworkInterface netInterface = netInterfaces.nextElement();
+                                               logger.log(DEBUG, "Interface:" + netInterface);
+                                               for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                                       InetAddress inetAddr = addr.getAddress();
+                                                       logger.log(DEBUG, "  addr: " + inetAddr);
+                                                       if (!inetAddr.isLoopbackAddress() && !inetAddr.isLinkLocalAddress()) {
+                                                               try {
+                                                                       if (inetAddr.isReachable((int) timeout)) {
+                                                                               networkAvailable = true;
+                                                                               duration = System.currentTimeMillis() - begin;
+                                                                               logger.log(DEBUG,
+                                                                                               "Network available after " + duration + " ms. IP: " + inetAddr);
+                                                                               break networkAvailable;
+                                                                       }
+                                                               } catch (IOException e) {
+                                                                       logger.log(ERROR, "Cannot check whether " + inetAddr + " is reachable", e);
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               } else {
+                                       throw new IllegalStateException("No network interface has been found");
+                               }
+                               try {
+                                       Thread.sleep(1000);
+                               } catch (InterruptedException e) {
+                                       // silent
+                               }
+                       }
+               } catch (Exception e) {
+                       logger.log(ERROR, "Cannot check whether network is available", e);
+               }
+               return networkAvailable;
+       }
+
+       static void shutdown(boolean reboot) {
+               try {
+                       stopInitDServices();
+                       Path sysrqP = Paths.get("/proc/sys/kernel/sysrq");
+                       Files.writeString(sysrqP, "1");
+                       Path sysrqTriggerP = Paths.get("/proc/sysrq-trigger");
+                       Files.writeString(sysrqTriggerP, "e");// send SIGTERM to all processes
+                       // Files.writeString(sysrqTriggerP, "i");// send SIGKILL to all processes
+                       Files.writeString(sysrqTriggerP, "e");// flush data to disk
+                       Files.writeString(sysrqTriggerP, "u");// unmount
+                       if (reboot)
+                               Files.writeString(sysrqTriggerP, "b");
+                       else
+                               Files.writeString(sysrqTriggerP, "o");
+               } catch (IOException e) {
+                       logger.log(ERROR, "Cannot shut down system", e);
+               }
+       }
+
+       static void stopInitDServices() {
+               for (int i = initDServices.size() - 1; i >= 0; i--) {
+                       String serviceName = initDServices.get(i);
+                       Path serviceInit = Paths.get("/etc/init.d/", serviceName);
+                       try {
+                               int exitCode = new ProcessBuilder(serviceInit.toString(), "stop").start().waitFor();
+                               if (exitCode != 0)
+                                       logger.log(ERROR, "Service " + serviceName + " dit not stop properly");
+                       } catch (InterruptedException | IOException e) {
+                               logger.log(ERROR, "Cannot stop service " + serviceName, e);
+                       }
+               }
+       }
+
+       /** A thread watching the login prompt. */
+       static class LoginThread extends Thread {
+               private boolean systemShuttingDown = false;
+               private Process process = null;
+
+               public LoginThread() {
+                       super("FREEd login prompt");
+                       setDaemon(true);
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+                               systemShuttingDown = true;
+                               if (process != null)
+                                       process.destroy();
+                       }));
+               }
+
+               @Override
+               public void run() {
+                       boolean getty = true;
+                       prompt: while (!systemShuttingDown) {
+                               try {
+                                       if (getty) {
+                                               ProcessBuilder pb = new ProcessBuilder("/usr/sbin/getty", "38400", "tty2");
+                                               process = pb.start();
+                                       } else {
+                                               Console console = System.console();
+                                               console.readLine(); // type return once to activate login prompt
+                                               console.printf("login: ");
+                                               String username = console.readLine();
+                                               username = username.trim();
+                                               if ("".equals(username))
+                                                       continue prompt;
+                                               ProcessBuilder pb = new ProcessBuilder("su", "--login", username);
+                                               pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+                                               pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
+                                               pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
+                                               process = pb.start();
+                                       }
+                                       Runtime.getRuntime().addShutdownHook(new Thread(() -> process.destroy()));
+                                       try {
+                                               process.waitFor();
+                                       } catch (InterruptedException e) {
+                                               process.destroy();
+                                       }
+                               } catch (Exception e) {
+                                       e.printStackTrace();
+                               } finally {
+                                       process = null;
+                               }
+                       }
+               }
+
+       }
+}