/**
* jfLinux Startup
* - includes org.jflinux.jfsystemmgr
*
* Created : Mar 31, 2012
*
* @author pquiring
*/
import java.io.*;
import java.util.*;
import javaforce.*;
import javaforce.jbus.*;
import javaforce.linux.Linux;
import javaforce.utils.monitordir;
public class Startup implements ShellProcessListener{
private static ShellProcess x11process;
private static boolean rebootFlag, shutdownFlag;
public static AutoMounter autoMounter;
public static JBusClient jbusClient;
/** Main entry point for jfLinux system.*/
public static void main(String args[]) {
JFLog.init("/var/log/jflogon.log", true);
JFLog.log("jLogon:Startup");
try {
fixSudoers();
Linux.init();
monitordir.init();
//start jfsystemmgr
jbusClient = new JBusClient("org.jflinux.jfsystemmgr", new JBusMethods());
jbusClient.start();
//start automounter
autoMounter = new AutoMounter();
autoMounter.start();
//start device monitor
new DeviceMonitor().start();
//stop plymouth
hidePlymouth();
//start X
create_server_xauth();
boolean retry;
do {
retry = false;
try {
startx();
} catch (Exception e) {
JFLog.log(e);
return;
}
JF.sleep(1500); //wait for X to start
Runtime.getRuntime().exec(new String[] {"numlockx", "on"});
try {
if (new File("/etc/.live").exists()) {
doLiveLogon();
} else {
createLogon();
}
} catch (java.awt.HeadlessException he) {
JFLog.log(he);
JF.sleep(500);
File xorgconf = new File("/etc/X11/xorg.conf");
if (xorgconf.exists()) {
JFLog.log("X Failed : Attempting to delete /etc/X11/xorg.conf and try again");
xorgconf.delete();
}
stopx();
retry = true;
} catch (Exception e) {
JFLog.log(e);
stopx();
retry = true;
}
} while (retry);
} catch (Exception e) {
JFLog.log(e);
}
}
private static void startx() throws Exception {
new Thread() {
public void run() {
x11process = new ShellProcess();
x11process.keepOutput(false);
x11process.addListener(new Startup());
JFLog.log("Starting X Server...");
x11process.run(new String[] {"/usr/bin/X"}, true);
//some options lightdm uses
// -core :0
// -seat seat0
// -nolisten tcp
// vt7
// -novtswitch
// -auth /var/run/lightdm/root/:0
}
}.start();
}
public static void stopx() throws Exception {
if (x11process != null) {
JFLog.log("Stopping X Server...");
x11process.destroy();
JF.sleep(500);
for(int a=0;a<3;a++) {
if (!x11process.isAlive()) break;
JF.sleep(1000);
}
if (x11process.isAlive()) {
x11process.destroyForcibly();
JF.sleep(500);
}
x11process = null;
JFLog.log("X Server stopped...");
}
}
public static byte mcookie[] = new byte[16];
private static void create_server_xauth() throws Exception {
//write auth data to /root/.Xauthority
Random r = new Random();
for(int a=0;a<16;a++) {
mcookie[a] = (byte)('a' + (Math.abs(r.nextInt()) % 26));
}
write_xauth("/root/.Xauthority");
}
private static void write_xauth(String fn) throws Exception {
FileOutputStream fos = new FileOutputStream(fn);
fos.write(new byte[] { (byte)0xfc, 0x00 }); //uint16 = 252
fos.write(new byte[] { 0x00, 0x00 }); //uint16 = 0 (string length)
fos.write(new byte[] { 0x00, 0x00 }); //uint16 = 0 (string length)
fos.write(new byte[] { 0x12, 0x00 }); //uint16 = 0x12 (string length)
fos.write("MIT-MAGIC-COOKIE-1".getBytes()); //magic string
fos.write(new byte[] { 0x10, 0x00 }); //uint16 = 0x10 (data length)
fos.write(mcookie); //cookie
fos.close();
}
private static void chown_xauth(String fn, String user) throws Exception {
try {
Runtime.getRuntime().exec(new String[] {"chown", user+":"+user, fn});
} catch (Exception e) {
JFLog.log(e);
}
}
private static void doLiveLogon() {
boolean casper = true;
try {
FileInputStream fis = new FileInputStream("/etc/.live");
Properties props = new Properties();
props.load(fis);
fis.close();
String user = props.getProperty("user");
if (user == null) user = "jflive";
String casperFlag = props.getProperty("casper");
if (casperFlag != null) casper = casperFlag.trim().equals("true");
//run session as live user
runSession(user, "/usr/bin/jfdesktop", null, null, false);
stopx();
JF.sleep(1000);
System.out.println("" + (char)0x1b + "[2J"); //clear screen
System.out.println("\n\n\n\n\n\t\tPlease remove installation media and reboot\n\n\n\n\n");
// shutdown("-H");
} catch (Exception e) {
JFLog.log(e);
}
}
public static void runSession(String user, String session, String env_names[], String env_values[], boolean domainLogon) {
try {
getUserDetails(user);
String xauthFile = homePath + "/.Xauthority";
write_xauth(xauthFile);
chown_xauth(xauthFile, user);
if (!Linux.isMemberOf(user, "audio")) {
//pulseaudio requires user to be member of 'audio' group
Runtime.getRuntime().exec(new String[] {"usermod", "-aG", "audio", user});
}
if (!Linux.isMemberOf(user, "sambashare")) {
//net usershare requires user to be member of 'sambashare' group
Runtime.getRuntime().exec(new String[] {"usermod", "-aG", "sambashare", user});
}
if (!Linux.isMemberOf(user, "video")) {
//video4linux requires user to be a member of 'video' group
Runtime.getRuntime().exec(new String[] {"usermod", "-aG", "video", user});
}
String jid = "j" + Math.abs(new Random().nextInt());
String cmd[] = new String[] {"/usr/bin/sudo", "-E", "-u", user,
domainLogon ? "/usr/sbin/jflogon-rundomain" : "/usr/sbin/jflogon-runsession",
session};
ProcessBuilder pb = new ProcessBuilder(cmd);
//sudo doesn't pass environment variables unless -E is used
//but leaving these next lines out causes problems
Map<String,String> env = pb.environment();
env.put("USER", user);
env.put("LOGNAME", user);
env.put("SHELL", shellPath);
env.put("HOME", homePath);
env.put("XAUTHORITY", homePath + "/.Xauthority");
env.put("JID", jid);
if (env_names != null) {
for(int a=0;a<env_names.length;a++) {
env.put(env_names[a], env_values[a]);
}
}
JFLog.log("Starting session:" + session + " for user " + user);
Process p = pb.start();
p.waitFor();
JFLog.log("Session has terminated");
JFLog.log("Killing all processes for user " + user);
Runtime.getRuntime().exec(new String[] {"killall", "-u", user}); //ensure session ended
JF.sleep(1500); //wait for windows to close
if (!globalConfig.disableSleep) {
if (rebootFlag) {
JFLog.log("Rebooting...");
rebootFlag = false;
Startup.reboot();
return;
}
if (shutdownFlag) {
JFLog.log("Shutting down...");
shutdownFlag = false;
Startup.shutdown("-P");
return;
}
} else {
JFLog.log("Power functions disabled by security policy.");
}
} catch (Exception e) {
JFLog.log(e);
}
}
public static String homePath, shellPath;
public static void getUserDetails(String user) throws Exception {
//find path from /etc/passwd
//passwd = user_name:x:uid:gid:full_name:home_dir:shell
FileInputStream fis = new FileInputStream("/etc/passwd");
int len = fis.available();
byte passwd[] = new byte[len];
fis.read(passwd);
String text = new String(passwd);
String lns[] = text.split("\n");
for(int ln=0;ln<lns.length;ln++) {
String fs[] = lns[ln].split(":");
if (!fs[0].equals(user)) continue;
homePath = fs[5];
shellPath = fs[6];
fis.close();
return;
}
fis.close();
throw new Exception("user not found");
}
/** Reboots PC */
public static void reboot() {
try {
stopx();
showPlymouth();
JFLog.log("Rebooting...");
Runtime.getRuntime().exec("reboot");
} catch (Exception e) {
JFLog.log(e);
}
}
/** Shuts down PC
* @param type "-P" = powerdown, "-H"=halt
*/
public static void shutdown(String type) {
try {
stopx();
showPlymouth();
JFLog.log("Shutting down...,type=" + type);
Runtime.getRuntime().exec("shutdown " + type + " now");
} catch (Exception e) {
JFLog.log(e);
}
}
public static void createLogon() {
Linux.x11_rr_reset("800x600");
//execute greeter
try {
Logon logon = new Logon();
logon.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void hidePlymouth() {
if (new File("/bin/plymouth").exists()) {
try {
Runtime.getRuntime().exec(new String[] {"/bin/plymouth","--quit"});
} catch (Exception e) {
JFLog.log(e);
}
}
}
//this is interferring with reboot/shutdown
private static void showPlymouth() {
if (new File("/bin/plymouth").exists()) {
try {
// Runtime.getRuntime().exec(new String[] {"/bin/plymouth","--show-splash"}); //causes reboot to fail
} catch (Exception e) {
JFLog.log(e);
}
}
}
public void shellProcessOutput(String string) {
}
/** sudoers can NOT require tty or jflogon (and other apps that use sudo) fail. */
public static void fixSudoers() {
try {
FileInputStream fis = new FileInputStream("/etc/sudoers");
byte data[] = JF.readAll(fis);
fis.close();
String sudoers = new String(data);
String lns[] = sudoers.split("\n");
boolean patched = false;
StringBuilder sb = new StringBuilder();
for(int a=0;a<lns.length;a++) {
if (lns[a].indexOf("requiretty") != -1 && !lns[a].startsWith("#")) {
lns[a] = "#" + lns[a];
patched = true;
}
sb.append(lns[a]);
sb.append("\n");
}
if (!patched) return;
FileOutputStream fos = new FileOutputStream("/etc/sudoers");
fos.write(sb.toString().getBytes());
fos.close();
} catch (Exception e) {
JFLog.log(e);
}
}
public static class GlobalConfig {
public boolean disableSleep;
}
private static GlobalConfig globalConfig = new GlobalConfig();
private String globalConfigFolder = "/etc/jconfig.d/";
private String globalConfigFile = "global.xml";
public void loadGlobalConfig() {
try {
XML xml = new XML();
FileInputStream fis = new FileInputStream(globalConfigFolder + "/" + globalConfigFile);
xml.read(fis);
xml.writeClass(globalConfig);
} catch (FileNotFoundException fnfe) {
defaultGlobalConfig();
} catch (Exception e) {
defaultGlobalConfig();
JFLog.log(e);
}
}
private void defaultGlobalConfig() {
globalConfig.disableSleep = false;
}
private static String quote(String str) {
return "\"" + str + "\"";
}
public static class JBusMethods {
public void sleep() {
if (globalConfig.disableSleep) return;
try {
Runtime.getRuntime().exec(new String[] {"systemctl", "suspend"});
} catch (Exception e) {
JFLog.log(e);
}
}
public void reboot() {
if (globalConfig.disableSleep) return;
JFLog.log("Reboot requested on Session stop");
rebootFlag = true;
}
public void shutdown() {
if (globalConfig.disableSleep) return;
JFLog.log("Shutdown requested on Session stop");
shutdownFlag = true;
}
public void upgradesAvailable(int upgrades) {
jbusClient.broadcast("org.jflinux.jfdesktop", "updatesAvailable", "" + upgrades);
}
public void mount(String dev) {
JFLog.log("mount:" + dev);
Startup.autoMounter.mount(dev);
}
public void umount(String path) {
JFLog.log("umount:" + path);
AutoMounter.Mount mount = Startup.autoMounter.getMount(path);
if (mount == null) {
JFLog.log("umount:" + path + ":Error:never mounted by AutoMounter");
//try to fix this
mount = new AutoMounter.Mount();
mount.media = path;
}
Startup.autoMounter.umount(mount);
}
private String cleanName(String name) {
//filter out bad chars in volume names
StringBuilder sb = new StringBuilder();
char in[] = name.toCharArray();
for(int a=0;a<in.length;a++) {
switch (in[a]) {
case ':':
case ';':
case '*':
case '?':
break;
default:
sb.append(in[a]);
break;
}
}
return sb.toString();
}
public void renameDevice(String media, String newName) {
newName = cleanName(newName);
if (newName.length() == 0) return;
//get device name
AutoMounter.Mount mount = Startup.autoMounter.getMount("/media/" + media);
if (mount == null) return;
if (mount.fs.equals("iso9660")) return;
//umount it
Startup.autoMounter.umount(mount.dev);
JF.sleep(500); //just in case
//change name
String cmd[] = {mount.fs + "fslabel", mount.dev, newName};
try {Runtime.getRuntime().exec(cmd);} catch (Exception e) {JFLog.log(e);}
JF.sleep(500); //this is needed
//mount it back
Startup.autoMounter.mount(mount.dev);
}
public void getStorageInfo(String pack, String dev) {
AutoMounter.Mount tmp = new AutoMounter.Mount();
String volName = Startup.autoMounter.getVolumeName(dev, tmp);
if (volName == null) volName = "";
if (tmp.fs == null) tmp.fs = "unknown";
String mountPt = Startup.autoMounter.getMountPoint(dev);
if (mountPt == null) mountPt = "";
jbusClient.call(pack, "storageInfo", quote(dev) + "," + quote(volName) + "," + quote(tmp.fs) + ","
+ quote(mountPt));
}
public void stopAutoMounter() {
AutoMounter.paused--;
}
public void startAutoMounter() {
AutoMounter.paused++;
}
public void broadcastWAPList(String list) {
jbusClient.broadcast("org.jflinux.jfdesktop.", "setWAPList", quote(list));
}
public void broadcastVideoChanged(String reason) {
jbusClient.broadcast("org.jflinux.jfdesktop.", "videoChanged", quote(reason));
jbusClient.broadcast("org.jflinux.jfconfig.", "videoChanged", quote(reason));
}
}
}