Core features of FREEd PID1 working in a systemd container
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 10 Dec 2023 10:56:19 +0000 (11:56 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 10 Dec 2023 10:56:19 +0000 (11:56 +0100)
etc/freed/pid1/config.ini
etc/freed/pid1/jvm.args
local.mk
sjbin/src/freed-pid1.java

index 3ed0e861a897dc1808d0b95b05f2b4ee522e5168..bcdf102d4995393004770e77aa907daa4568869d 100644 (file)
@@ -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=\
index 2a6402a97465a191c5bf29620b5a85100093f006..572fe84c70162e31591225ca1da838f5f5f3060b 100644 (file)
@@ -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
index c97b72e8a851dac8e70455d90c905afe9ebcc2cd..7095276ba9b6f8a9e69e58aeb4f343942300be86 100644 (file)
--- 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)
index 9dd1c313714d6f9f0fb8f679d3bb04e1b3aa59c1..31865bfa03596aac108781d94b378d86f6469001 100644 (file)
@@ -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<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();
-                                       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<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();
+                                               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;
+                               }
+                       }
+               }
+
+       }
 }