]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.init/src/org/argeo/init/SysInitMain.java
Refactor Argeo init
[lgpl/argeo-commons.git] / org.argeo.init / src / org / argeo / init / SysInitMain.java
1 package org.argeo.init;
2 //#! /usr/bin/java --source 17 @/usr/local/etc/freed/pid1/jvm.args
3
4 import static java.lang.System.Logger.Level.DEBUG;
5 import static java.lang.System.Logger.Level.ERROR;
6 import static java.lang.System.Logger.Level.INFO;
7 import static java.lang.System.Logger.Level.WARNING;
8
9 import java.io.Console;
10 import java.io.IOException;
11 import java.lang.System.Logger;
12 import java.lang.management.ManagementFactory;
13 import java.net.InetAddress;
14 import java.net.InterfaceAddress;
15 import java.net.NetworkInterface;
16 import java.net.SocketException;
17 import java.nio.file.Files;
18 import java.nio.file.Path;
19 import java.nio.file.Paths;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Enumeration;
23 import java.util.List;
24 import java.util.TreeMap;
25 import java.util.concurrent.atomic.AtomicInteger;
26
27 import sun.misc.Signal;
28
29 /** A minimalistic Linux init process. */
30 class SysInitMain {
31 final static AtomicInteger runLevel = new AtomicInteger(-1);
32
33 private final static Logger logger = System.getLogger(SysInitMain.class.getName());
34
35 private final static List<String> initDServices = Collections.synchronizedList(new ArrayList<>());
36
37 public static void main(String... args) {
38 try {
39 final long pid = ProcessHandle.current().pid();
40 Signal.handle(new Signal("TERM"), (signal) -> {
41 System.out.println("SIGTERM caught");
42 System.exit(0);
43 });
44 Signal.handle(new Signal("INT"), (signal) -> {
45 System.out.println("SIGINT caught");
46 System.exit(0);
47 });
48 Signal.handle(new Signal("HUP"), (signal) -> {
49 System.out.println("SIGHUP caught");
50 System.exit(0);
51 });
52
53 boolean isSystemInit = pid == 1 || pid == 2;
54
55 if (isSystemInit && args.length > 0 && ("1".equals(args[0]) //
56 || "single".equals(args[0]) //
57 || "emergency".equals(args[0]))) {
58 runLevel.set(1);
59 for (Object key : new TreeMap<>(System.getProperties()).keySet()) {
60 System.out.println(key + "=" + System.getProperty(key.toString()));
61 }
62 System.out.println("Single user mode");
63 System.out.flush();
64 ProcessBuilder pb = new ProcessBuilder("/bin/bash");
65 pb.redirectError(ProcessBuilder.Redirect.INHERIT);
66 pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
67 pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
68 Process singleUserShell = pb.start();
69 singleUserShell.waitFor();
70 } else {
71 if (args.length == 0)
72 runLevel.set(5);
73 else
74 runLevel.set(Integer.parseInt(args[0]));
75
76 if (runLevel.get() == 0) {// shutting down the whole system
77 if (!isSystemInit) {
78 logger.log(INFO, "Shutting down system...");
79 shutdown(false);
80 System.exit(0);
81 } else {
82 logger.log(ERROR, "Cannot start at run level " + runLevel.get());
83 System.exit(1);
84 }
85 } else if (runLevel.get() == 6) {// reboot the whole system
86 if (!isSystemInit) {
87 logger.log(INFO, "Rebooting the system...");
88 shutdown(true);
89 } else {
90 logger.log(ERROR, "Cannot start at run level " + runLevel.get());
91 System.exit(1);
92 }
93 }
94
95 logger.log(INFO, "FREEd Init daemon starting with pid " + pid + " after "
96 + ManagementFactory.getRuntimeMXBean().getUptime() + " ms");
97 // hostname
98 String hostname = Files.readString(Paths.get("/etc/hostname"));
99 new ProcessBuilder("/usr/bin/hostname", hostname).start();
100 logger.log(DEBUG, "Set hostname to " + hostname);
101 // networking
102 initSysctl();
103 startInitDService("networking", true);
104 // Thread.sleep(3000);// leave some time for network to start up
105 if (!waitForNetwork(10 * 1000))
106 logger.log(ERROR, "No network available");
107
108 // OpenSSH
109 // TODO make it coherent with Java sshd
110 startInitDService("ssh", true);
111
112 // NSS services
113 startInitDService("nslcd", false);// Note: nslcd fails to stop
114
115 // login prompt
116 ServiceMain.addPostStart(() -> new LoginThread().start());
117
118 // init Argeo CMS
119 logger.log(INFO, "FREEd Init daemon starting Argeo Init after "
120 + ManagementFactory.getRuntimeMXBean().getUptime() + " ms");
121 ServiceMain.main(args);
122 }
123 } catch (Throwable e) {
124 logger.log(ERROR, "Unexpected exception in free-pid1 init, shutting down... ", e);
125 System.exit(1);
126 } finally {
127 stopInitDServices();
128 }
129 }
130
131 static void initSysctl() {
132 try {
133 Path sysctlD = Paths.get("/etc/sysctl.d/");
134 for (Path conf : Files.newDirectoryStream(sysctlD, "*.conf")) {
135 try {
136 new ProcessBuilder("/usr/sbin/sysctl", "-p", conf.toString()).start();
137 } catch (IOException e) {
138 e.printStackTrace();
139 }
140 }
141 } catch (IOException e) {
142 e.printStackTrace();
143 }
144 }
145
146 static void startInitDService(String serviceName, boolean stopOnShutdown) {
147 Path serviceInit = Paths.get("/etc/init.d/", serviceName);
148 if (Files.exists(serviceInit))
149 try {
150 int exitCode = new ProcessBuilder(serviceInit.toString(), "start").start().waitFor();
151 if (exitCode != 0)
152 logger.log(ERROR, "Service " + serviceName + " dit not stop properly");
153 else
154 logger.log(DEBUG, "Service " + serviceName + " started");
155 if (stopOnShutdown)
156 initDServices.add(serviceName);
157 // Runtime.getRuntime().addShutdownHook(new Thread(() -> {
158 // try {
159 // new ProcessBuilder(serviceInit.toString(), "stop").start().waitFor();
160 // } catch (IOException | InterruptedException e) {
161 // e.printStackTrace();
162 // }
163 // }, "FREEd stop service " + serviceName));
164 } catch (IOException | InterruptedException e) {
165 e.printStackTrace();
166 }
167 else
168 logger.log(WARNING, "Service " + serviceName + " not found and therefore not started");
169 }
170
171 static boolean waitForNetwork(long timeout) {
172 long begin = System.currentTimeMillis();
173 long duration = 0;
174 boolean networkAvailable = false;
175 try {
176 networkAvailable: while (!networkAvailable) {
177 duration = System.currentTimeMillis() - begin;
178 if (duration > timeout)
179 break networkAvailable;
180 Enumeration<NetworkInterface> netInterfaces = null;
181 try {
182 netInterfaces = NetworkInterface.getNetworkInterfaces();
183 } catch (SocketException e) {
184 throw new IllegalStateException("Cannot list network interfaces", e);
185 }
186 if (netInterfaces != null) {
187 while (netInterfaces.hasMoreElements()) {
188 NetworkInterface netInterface = netInterfaces.nextElement();
189 logger.log(DEBUG, "Interface:" + netInterface);
190 for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
191 InetAddress inetAddr = addr.getAddress();
192 logger.log(DEBUG, " addr: " + inetAddr);
193 if (!inetAddr.isLoopbackAddress() && !inetAddr.isLinkLocalAddress()) {
194 try {
195 if (inetAddr.isReachable((int) timeout)) {
196 networkAvailable = true;
197 duration = System.currentTimeMillis() - begin;
198 logger.log(DEBUG,
199 "Network available after " + duration + " ms. IP: " + inetAddr);
200 break networkAvailable;
201 }
202 } catch (IOException e) {
203 logger.log(ERROR, "Cannot check whether " + inetAddr + " is reachable", e);
204 }
205 }
206 }
207 }
208 } else {
209 throw new IllegalStateException("No network interface has been found");
210 }
211 try {
212 Thread.sleep(1000);
213 } catch (InterruptedException e) {
214 // silent
215 }
216 }
217 } catch (Exception e) {
218 logger.log(ERROR, "Cannot check whether network is available", e);
219 }
220 return networkAvailable;
221 }
222
223 static void shutdown(boolean reboot) {
224 try {
225 stopInitDServices();
226 Path sysrqP = Paths.get("/proc/sys/kernel/sysrq");
227 Files.writeString(sysrqP, "1");
228 Path sysrqTriggerP = Paths.get("/proc/sysrq-trigger");
229 Files.writeString(sysrqTriggerP, "e");// send SIGTERM to all processes
230 // Files.writeString(sysrqTriggerP, "i");// send SIGKILL to all processes
231 Files.writeString(sysrqTriggerP, "e");// flush data to disk
232 Files.writeString(sysrqTriggerP, "u");// unmount
233 if (reboot)
234 Files.writeString(sysrqTriggerP, "b");
235 else
236 Files.writeString(sysrqTriggerP, "o");
237 } catch (IOException e) {
238 logger.log(ERROR, "Cannot shut down system", e);
239 }
240 }
241
242 static void stopInitDServices() {
243 for (int i = initDServices.size() - 1; i >= 0; i--) {
244 String serviceName = initDServices.get(i);
245 Path serviceInit = Paths.get("/etc/init.d/", serviceName);
246 try {
247 int exitCode = new ProcessBuilder(serviceInit.toString(), "stop").start().waitFor();
248 if (exitCode != 0)
249 logger.log(ERROR, "Service " + serviceName + " dit not stop properly");
250 } catch (InterruptedException | IOException e) {
251 logger.log(ERROR, "Cannot stop service " + serviceName, e);
252 }
253 }
254 }
255
256 /** A thread watching the login prompt. */
257 static class LoginThread extends Thread {
258 private boolean systemShuttingDown = false;
259 private Process process = null;
260
261 public LoginThread() {
262 super("FREEd login prompt");
263 setDaemon(true);
264 Runtime.getRuntime().addShutdownHook(new Thread(() -> {
265 systemShuttingDown = true;
266 if (process != null)
267 process.destroy();
268 }));
269 }
270
271 @Override
272 public void run() {
273 boolean getty = true;
274 prompt: while (!systemShuttingDown) {
275 try {
276 if (getty) {
277 ProcessBuilder pb = new ProcessBuilder("/usr/sbin/getty", "38400", "tty2");
278 process = pb.start();
279 } else {
280 Console console = System.console();
281 console.readLine(); // type return once to activate login prompt
282 console.printf("login: ");
283 String username = console.readLine();
284 username = username.trim();
285 if ("".equals(username))
286 continue prompt;
287 ProcessBuilder pb = new ProcessBuilder("su", "--login", username);
288 pb.redirectError(ProcessBuilder.Redirect.INHERIT);
289 pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
290 pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
291 process = pb.start();
292 }
293 Runtime.getRuntime().addShutdownHook(new Thread(() -> process.destroy()));
294 try {
295 process.waitFor();
296 } catch (InterruptedException e) {
297 process.destroy();
298 }
299 } catch (Exception e) {
300 e.printStackTrace();
301 } finally {
302 process = null;
303 }
304 }
305 }
306
307 }
308 }