X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=blobdiff_plain;f=org.argeo.init%2Fsrc%2Forg%2Fargeo%2Finit%2FSysInitMain.java;fp=org.argeo.init%2Fsrc%2Forg%2Fargeo%2Finit%2FSysInitMain.java;h=8e59c24e0ef1283e5e70972ac47b17695b8ad5a4;hp=0000000000000000000000000000000000000000;hb=b95462873703848193e56fcbe997693630db6121;hpb=55d88fba80cec198a0f11ba7545e19878c51fc5e 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 index 000000000..8e59c24e0 --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/SysInitMain.java @@ -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 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 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; + } + } + } + + } +}