From a6339ac17f9a52fdb524aef2dc626b30d62a8b54 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sun, 10 Dec 2023 11:56:19 +0100 Subject: [PATCH] Core features of FREEd PID1 working in a systemd container --- etc/freed/pid1/config.ini | 2 +- etc/freed/pid1/jvm.args | 6 + local.mk | 2 +- sjbin/src/freed-pid1.java | 244 +++++++++++++++++++++++++------------- 4 files changed, 167 insertions(+), 87 deletions(-) diff --git a/etc/freed/pid1/config.ini b/etc/freed/pid1/config.ini index 3ed0e86..bcdf102 100644 --- a/etc/freed/pid1/config.ini +++ b/etc/freed/pid1/config.ini @@ -18,7 +18,7 @@ org.argeo.cms.lib.equinox,\ org.argeo.cms.lib.jetty,\ org.argeo.cms.jshell,\ -#argeo.http.port=80 +argeo.http.port=80 argeo.sshd.port=22 argeo.osgi.sources=\ diff --git a/etc/freed/pid1/jvm.args b/etc/freed/pid1/jvm.args index 2a6402a..572fe84 100644 --- a/etc/freed/pid1/jvm.args +++ b/etc/freed/pid1/jvm.args @@ -1,3 +1,9 @@ +#-Xmx64m +#-Xshareclasses:name=pid1 +#-XX:+IdleTuningGcOnIdle + +-Dlog.FreedPid1=DEBUG + -Dosgi.configuration.cascaded=true -Dosgi.sharedConfiguration.area=/usr/local/etc/freed/pid1 -Dosgi.sharedConfiguration.area.readOnly=true diff --git a/local.mk b/local.mk index c97b72e..7095276 100644 --- a/local.mk +++ b/local.mk @@ -18,7 +18,7 @@ clean: $(MAKE) -C sjbin clean install: - $(COPY) -r etc/* $(DESTDIR)$(sysconfdir) + $(COPY) -r --no-clobber etc/* $(DESTDIR)$(sysconfdir) $(COPY) -r usr/bin/* $(DESTDIR)$(bindir) $(COPY) -r usr/share/* $(DESTDIR)$(datarootdir) $(COPY) -r usr/lib/* $(DESTDIR)$(libdir) diff --git a/sjbin/src/freed-pid1.java b/sjbin/src/freed-pid1.java index 9dd1c31..31865bf 100644 --- a/sjbin/src/freed-pid1.java +++ b/sjbin/src/freed-pid1.java @@ -1,6 +1,13 @@ //#! /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; @@ -11,28 +18,20 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Enumeration; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; import org.argeo.init.Service; -import jdk.jshell.tool.JavaShellToolBuilder; import sun.misc.Signal; class FreedPid1 { + final static AtomicInteger runLevel = new AtomicInteger(-1); + + private final static Logger logger = System.getLogger(FreedPid1.class.getName()); + public static void main(String... args) { try { final long pid = ProcessHandle.current().pid(); - System.out.println("FREEd Init daemon starting with pid " + pid + "..."); -// System.out.println(System.getProperty("user.dir")); -// System.out.println(System.getProperty("user.name")); -// System.out.println(System.getProperty("user.home")); - - // System.setProperty("user.dir", "/tmp"); - for (Object key : new TreeMap<>(System.getProperties()).keySet()) { - System.out.println(key + "=" + System.getProperty(key.toString())); - } - - flushStd(); - Signal.handle(new Signal("TERM"), (signal) -> { System.out.println("SIGTERM caught"); System.exit(0); @@ -46,46 +45,74 @@ class FreedPid1 { System.exit(0); }); - if (args.length > 0 && ("1".equals(args[0]) // + boolean isSystemInit = pid == 1 || pid == 2; + + if (isSystemInit && args.length > 0 && ("1".equals(args[0]) // || "single".equals(args[0]) // || "emergency".equals(args[0]))) { - // TODO check if we can remove dependency to management - String classpath = ManagementFactory.getRuntimeMXBean().getClassPath(); - String feedbackMode = "concise"; - // TODO --startup script - JavaShellToolBuilder builder = JavaShellToolBuilder.builder(); - try { - builder.start("--execution", "direct", "--class-path", classpath, "--feedback", feedbackMode); - } catch (Exception e) { - e.printStackTrace(); - System.err.flush(); - System.exit(1); - return; + 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 { - // init Linux services + 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..."); + new ProcessBuilder("/usr/bin/kill", "1").start(); + 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 down system..."); + Path sysrqP = Paths.get("/proc/sys/kernel/sysrq"); + Files.writeString(sysrqP, "1"); + Path sysrqTriggerP = Paths.get("/proc/sysrq-trigger"); + Files.writeString(sysrqTriggerP, "b"); + System.exit(0); + } 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"); + // networking initSysctl(); - startInitDService("networking"); - flushStd(); + startInitDService("networking", true); + if (!waitForNetwork(30 * 1000)) + logger.log(ERROR, "No network available"); - if (!waitForNetwork(30 * 1000)) { - System.err.println("No network available"); - } - flushStd(); + // NSS services + startInitDService("nslcd", false);// Note: nslcd fails to stop - startInitDService("nslcd"); + // login prompt + Service.addPostStart(() -> new LoginThread().start()); // init Argeo CMS + logger.log(INFO, "FREEd Init daemon starting Argeo Init after " + + ManagementFactory.getRuntimeMXBean().getUptime() + " ms"); Service.main(args); } } catch (Throwable e) { - System.err.println("Unexpected exception in free-pid1 init, shutting down... " + e.getMessage()); - e.printStackTrace(); - flushStd(); + logger.log(ERROR, "Unexpected exception in free-pid1 init, shutting down... ", e); System.exit(1); -// if(e instanceof Error) -// throw e; } } @@ -104,74 +131,121 @@ class FreedPid1 { } } - static void startInitDService(String serviceName) { + static void startInitDService(String serviceName, boolean stopOnShutdown) { Path serviceInit = Paths.get("/etc/init.d/", serviceName); if (Files.exists(serviceInit)) try { new ProcessBuilder(serviceInit.toString(), "start").start().waitFor(); - System.out.println("Service " + serviceName + " started"); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - new ProcessBuilder(serviceInit.toString(), "stop").start().waitFor(); - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - }, "Stop service " + serviceName)); + logger.log(DEBUG, "Service " + serviceName + " started"); + if (stopOnShutdown) + 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 - System.out.println("Service " + serviceName + " not found and therefore not started"); + 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; - 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(); - for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) { - InetAddress inetAddr = addr.getAddress(); - if (!inetAddr.isLoopbackAddress() && !inetAddr.isLinkLocalAddress()) { - try { - if (inetAddr.isReachable((int) timeout)) { - networkAvailable = true; - System.out.println("Network available after " + duration + " ms. IP: " + inetAddr); - break networkAvailable; + 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(); + for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) { + InetAddress inetAddr = addr.getAddress(); + 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); } - } catch (IOException e) { - System.err.println( - "Cannot check whether " + inetAddr + " is reachable: " + e.getMessage()); } } } + } else { + throw new IllegalStateException("No network interface has been found"); + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // silent } - } else { - throw new IllegalStateException("No network interface has been found"); - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); } + } catch (Exception e) { + logger.log(ERROR, "Cannot check whether network is avialable", e); } return networkAvailable; } - static void flushStd() { - System.err.flush(); - System.out.flush(); - } + /** 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() { + Console console = System.console(); + console.readLine(); // type return once to activate login prompt + prompt: while (!systemShuttingDown) { + try { + 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; + } + } + } + + } } -- 2.30.2