package fq.router2.life_cycle;
import android.content.Context;
import android.os.Build;
import fq.router2.R;
import fq.router2.feedback.HandleAlertIntent;
import fq.router2.utils.IOUtils;
import fq.router2.utils.LogUtils;
import fq.router2.utils.ShellUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
public class Deployer {
public static File PAYLOAD_ZIP = new File(ShellUtils.DATA_DIR, "payload.zip");
public static File PAYLOAD_CHECKSUM = new File(ShellUtils.DATA_DIR, "payload.checksum");
public static File PYTHON_DIR = new File(ShellUtils.DATA_DIR, "python");
public static File PYTHON_LAUNCHER = new File(PYTHON_DIR, "bin/python");
public static File WIFI_TOOLS_DIR = new File(ShellUtils.DATA_DIR, "wifi-tools");
public static File PROXY_TOOLS_DIR = new File(ShellUtils.DATA_DIR, "proxy-tools");
public static File MANAGER_DIR = new File(ShellUtils.DATA_DIR, "manager");
public static File MANAGER_MAIN_PY = new File(MANAGER_DIR, "main.py");
public static File MANAGER_VPN_PY = new File(MANAGER_DIR, "vpn.py");
private final Context context;
public Deployer(Context context) {
this.context = context;
}
public String deploy() {
LogUtils.i("Deploying payload");
try {
copyBusybox();
makeExecutable(ShellUtils.BUSYBOX_FILE);
} catch (Exception e) {
return logFatalError("failed to copy busybox", e);
}
boolean foundPayloadUpdate;
try {
foundPayloadUpdate = shouldDeployPayload();
} catch (Exception e) {
return logFatalError("failed to check update", e);
}
if (foundPayloadUpdate) {
context.sendBroadcast(new LaunchingIntent(_(R.string.status_clear_old_files), 30));
try {
try {
ManagerProcess.kill();
} catch (Exception e) {
LogUtils.e("failed to kill manager before redeploy", e);
// ignore and continue
}
clearDataDirectory();
} catch (Exception e) {
return logFatalError("failed to clear data directory", e);
}
}
context.sendBroadcast(new LaunchingIntent(_(R.string.status_copy_files), 35));
try {
copyBusybox();
makeExecutable(ShellUtils.BUSYBOX_FILE);
copyPayloadZip();
} catch (Exception e) {
return logFatalError("failed to copy payload.zip", e);
}
context.sendBroadcast(new LaunchingIntent(_(R.string.status_unzip_files), 40));
try {
unzipPayloadZip();
} catch (Exception e) {
String msg = logFatalError("failed to unzip payload.zip", e);
try {
if (ShellUtils.execute("getprop", "ro.product.cpu.abi").trim().equals("x86")) {
return _(R.string.x86_is_not_supported);
}
} catch (Exception e2) {
// ignore
}
return msg;
}
try {
linkLibs();
} catch (Exception e) {
LogUtils.e("failed to link libs", e);
}
try {
makePayloadExecutable();
} catch (Exception e) {
return logFatalError("failed to make payload executable", e);
}
LogUtils.i("Deployed payload");
return "";
}
private String _(int id) {
return context.getResources().getString(id);
}
private boolean shouldDeployPayload() throws Exception {
if (!PAYLOAD_CHECKSUM.exists()) {
LogUtils.i("no checksum, assume it is old");
return true;
}
if (!isPayloadComplete()) {
LogUtils.i("payload is corrupted");
return true;
}
String oldChecksum = IOUtils.readFromFile(PAYLOAD_CHECKSUM);
InputStream inputStream = context.getAssets().open("payload.zip");
try {
String newChecksum = IOUtils.copy(inputStream, null);
if (oldChecksum.equals(newChecksum)) {
LogUtils.i("no payload update found");
return false;
} else {
LogUtils.i("found payload update");
return true;
}
} finally {
inputStream.close();
}
}
private boolean isPayloadComplete() {
return MANAGER_VPN_PY.exists() && PYTHON_LAUNCHER.exists() && WIFI_TOOLS_DIR.exists();
}
private void clearDataDirectory() throws Exception {
if (ShellUtils.DATA_DIR.exists()) {
LogUtils.i("clear data dir");
deleteDirectory(ShellUtils.DATA_DIR + "/python");
deleteDirectory(ShellUtils.DATA_DIR + "/wifi-tools");
deleteDirectory(ShellUtils.DATA_DIR + "/proxy-tools");
deleteDirectory(ShellUtils.DATA_DIR + "/manager");
deleteDirectory(ShellUtils.DATA_DIR + "/payload.zip");
new File("/data/data/fq.router2/busybox").delete();
}
}
private void deleteDirectory(String path) throws Exception {
if (new File(path).exists()) {
try {
ShellUtils.execute("/data/data/fq.router2/busybox", "rm", "-rf", path);
} catch (Exception e) {
LogUtils.e("failed to delete " + path, e);
}
}
if (new File(path).exists()) {
LogUtils.e("failed to delete " + path);
}
}
private void copyPayloadZip() throws Exception {
if (PAYLOAD_ZIP.exists()) {
LogUtils.i("skip copy payload.zip as it already exists");
return;
}
if (PYTHON_DIR.exists()) {
LogUtils.i("skip copy payload.zip as it has already been unzipped");
return;
}
LogUtils.i("copying payload.zip to data directory");
InputStream inputStream = context.getAssets().open("payload.zip");
try {
OutputStream outputStream = new FileOutputStream(PAYLOAD_ZIP);
try {
String checksum = IOUtils.copy(inputStream, outputStream);
IOUtils.writeToFile(PAYLOAD_CHECKSUM, checksum);
} finally {
outputStream.close();
}
} finally {
inputStream.close();
}
LogUtils.i("successfully copied payload.zip");
}
private void copyBusybox() throws Exception {
if (ShellUtils.BUSYBOX_FILE.exists()) {
LogUtils.i("skip copy busybox as it already exists");
return;
}
LogUtils.i("copying busybox to data directory");
InputStream inputStream = context.getAssets().open("busybox");
try {
OutputStream outputStream = new FileOutputStream(ShellUtils.BUSYBOX_FILE);
try {
IOUtils.copy(inputStream, outputStream);
} finally {
outputStream.close();
}
} finally {
inputStream.close();
}
LogUtils.i("successfully copied busybox");
}
private void unzipPayloadZip() throws Exception {
if (PYTHON_DIR.exists()) {
LogUtils.i("skip unzip payload.zip as it has already been unzipped");
return;
}
LogUtils.i("unzipping payload.zip");
Process process = Runtime.getRuntime().exec(
ShellUtils.BUSYBOX_FILE + " unzip -o -q payload.zip", new String[0], ShellUtils.DATA_DIR);
try {
ShellUtils.waitFor("unzip", process);
} catch (Exception e) {
LogUtils.e("unzip failed", e);
Thread.sleep(3000);
process = Runtime.getRuntime().exec(
ShellUtils.BUSYBOX_FILE + " unzip -o -q payload.zip", new String[0], ShellUtils.DATA_DIR);
ShellUtils.waitFor("unzip", process);
}
if (!new File("/data/data/fq.router2/payload.zip").delete()) {
LogUtils.i("failed to delete payload.zip after unzip");
}
for (int i = 0; i < 10; i++) {
LogUtils.i("sleep 2 seconds");
Thread.sleep(2000); // wait for the files written out
if (isPayloadComplete()) {
LogUtils.i("payload is complete");
break;
}
}
LogUtils.i("successfully unzipped payload.zip");
}
private void linkLibs() throws Exception {
if (Build.VERSION.SDK_INT >= 14) {
return; // try narrow the effect range
}
if (isXperia()) {
LogUtils.i("skip link libs for xperia"); // will cause reboot
return;
}
if (!shouldLinkLibs()) {
return;
}
ShellUtils.sudo("/data/data/fq.router2/busybox", "mount", "-o", "remount,rw", "/system");
try {
File[] files = new File(PYTHON_DIR, "lib").listFiles();
if (files == null) {
throw new Exception(new File(PYTHON_DIR, "lib") + " not found");
} else {
for (File file : files) {
String targetPath = "/system/lib/" + file.getName();
if (!new File(targetPath).exists()) {
try {
LogUtils.i("rm " + targetPath);
ShellUtils.sudo("rm " + targetPath);
} catch (Exception e) {
// ignore
}
ShellUtils.sudo(ShellUtils.BUSYBOX_FILE.getCanonicalPath(), "ln", "-s", file.getCanonicalPath(), targetPath);
}
}
}
} finally {
ShellUtils.sudo("/data/data/fq.router2/busybox", "mount", "-o", "remount,ro", "/system");
}
}
private boolean isXperia() {
try {
return new File("/sbin/ric").exists();
} catch (Exception e) {
LogUtils.e("failed to tell if it is xperia", e);
return true;
}
}
private boolean shouldLinkLibs() throws Exception {
File[] files = new File(PYTHON_DIR, "lib").listFiles();
if (files == null) {
throw new Exception(new File(PYTHON_DIR, "lib") + " not found");
} else {
for (File file : files) {
String targetPath = "/system/lib/" + file.getName();
if (!new File(targetPath).exists()) {
return true;
}
}
}
return false;
}
private void makePayloadExecutable() throws Exception {
File[] files = new File(PYTHON_DIR, "bin").listFiles();
if (files == null) {
throw new Exception(new File(PYTHON_DIR, "bin") + " not found");
} else {
for (File file : files) {
makeExecutable(file);
}
}
try {
files = WIFI_TOOLS_DIR.listFiles();
if (files == null) {
throw new Exception(WIFI_TOOLS_DIR + " not found");
} else {
for (File file : files) {
makeExecutable(file);
}
}
} catch (Exception e) {
LogUtils.e("failed to make wifi tools executable", e);
}
// files = PROXY_TOOLS_DIR.listFiles();
// if (files == null) {
// throw new Exception(PROXY_TOOLS_DIR + " not found");
// } else {
// for (File file : files) {
// makeExecutable(file);
// }
// }
}
private void makeExecutable(File file) throws Exception {
try {
Method setExecutableMethod = File.class.getMethod("setExecutable", boolean.class, boolean.class);
if ((Boolean) setExecutableMethod.invoke(file, true, true)) {
LogUtils.i("successfully made " + file.getName() + " executable");
} else {
LogUtils.i("failed to make " + file.getName() + " executable");
ShellUtils.sudo(ShellUtils.findCommand("chmod"), "0700", file.getCanonicalPath());
}
} catch (NoSuchMethodException e) {
ShellUtils.execute("/data/data/fq.router2/busybox", "chmod", "0700", file.getAbsolutePath());
LogUtils.i("successfully made " + file.getName() + " executable");
}
}
private String logFatalError(String message, Exception e) {
LogUtils.e(message, e);
return message;
}
}