//#! /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.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 org.argeo.init.Service;
-import jdk.jshell.tool.JavaShellToolBuilder;
import sun.misc.Signal;
+/** A minimalistic Linux init process. */
class FreedPid1 {
+ final static AtomicInteger runLevel = new AtomicInteger(-1);
+
+ private final static Logger logger = System.getLogger(FreedPid1.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();
- 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);
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
- initSysctl();
- startInitDService("networking");
- flushStd();
+ if (args.length == 0)
+ runLevel.set(5);
+ else
+ runLevel.set(Integer.parseInt(args[0]));
- if (!waitForNetwork(30 * 1000)) {
- System.err.println("No network available");
+ 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);
+ }
}
- flushStd();
- startInitDService("nslcd");
+ 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
+ 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;
+ } finally {
+ stopInitDServices();
}
}
}
}
- 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));
+ 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
- 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();
+ 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);
}
- } 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(1000);
+ } 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 available", e);
}
return networkAvailable;
}
- static void flushStd() {
- System.err.flush();
- System.out.flush();
+ 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;
+ }
+ }
+ }
+
+ }
}