ToolsInstaller: safer state machine

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2018-06-10 05:17:09 +02:00
parent ea72e8b656
commit 15e10d8fde
3 changed files with 42 additions and 39 deletions

View File

@ -11,9 +11,11 @@ import android.support.annotation.NonNull;
import android.support.v7.preference.Preference; import android.support.v7.preference.Preference;
import android.system.OsConstants; import android.system.OsConstants;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import com.wireguard.android.Application; import com.wireguard.android.Application;
import com.wireguard.android.R; import com.wireguard.android.R;
import com.wireguard.android.util.ToolsInstaller;
/** /**
* Preference implementing a button that asynchronously runs {@code ToolsInstaller} and displays the * Preference implementing a button that asynchronously runs {@code ToolsInstaller} and displays the
@ -43,28 +45,34 @@ public class ToolsInstallerPreference extends Preference {
Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::areInstalled).whenComplete(this::onCheckResult); Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::areInstalled).whenComplete(this::onCheckResult);
} }
private void onCheckResult(final Integer result, final Throwable throwable) { private void onCheckResult(final int state, final Throwable throwable) {
setState(throwable == null && result == OsConstants.EALREADY ? if (throwable != null || state == ToolsInstaller.ERROR)
State.ALREADY : initialState()); setState(State.INITIAL);
else if ((state & ToolsInstaller.YES) == ToolsInstaller.YES)
setState(State.ALREADY);
else if ((state & (ToolsInstaller.MAGISK | ToolsInstaller.NO)) == (ToolsInstaller.MAGISK | ToolsInstaller.NO))
setState(State.INITIAL_MAGISK);
else if ((state & (ToolsInstaller.SYSTEM | ToolsInstaller.NO)) == (ToolsInstaller.SYSTEM | ToolsInstaller.NO))
setState(State.INITIAL_SYSTEM);
else
setState(State.INITIAL);
} }
@Override @Override
protected void onClick() { protected void onClick() {
setState(workingState()); setState(State.WORKING);
Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::install).whenComplete(this::onInstallResult); Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::install).whenComplete(this::onInstallResult);
} }
private void onInstallResult(final Integer result, final Throwable throwable) { private void onInstallResult(final Integer result, final Throwable throwable) {
final State nextState;
if (throwable != null) if (throwable != null)
nextState = State.FAILURE; setState(State.FAILURE);
else if (result == OsConstants.EXIT_SUCCESS) else if ((result & (ToolsInstaller.YES | ToolsInstaller.MAGISK)) == (ToolsInstaller.YES | ToolsInstaller.MAGISK))
nextState = successState(); setState(State.SUCCESS_MAGISK);
else if (result == OsConstants.EALREADY) else if ((result & (ToolsInstaller.YES | ToolsInstaller.SYSTEM)) == (ToolsInstaller.YES | ToolsInstaller.SYSTEM))
nextState = State.ALREADY; setState(State.SUCCESS_SYSTEM);
else else
nextState = State.FAILURE; setState(State.FAILURE);
setState(nextState);
} }
private void setState(@NonNull final State state) { private void setState(@NonNull final State state) {
@ -76,26 +84,15 @@ public class ToolsInstallerPreference extends Preference {
notifyChanged(); notifyChanged();
} }
private State initialState() {
return Application.getToolsInstaller().willInstallAsMagiskModule(false) ? State.INITIAL_MAGISK : State.INITIAL_SYSTEM;
}
private State workingState() {
return Application.getToolsInstaller().willInstallAsMagiskModule(false) ? State.WORKING_MAGISK : State.WORKING_SYSTEM;
}
private State successState() {
return Application.getToolsInstaller().willInstallAsMagiskModule(false) ? State.SUCCESS_MAGISK : State.SUCCESS_SYSTEM;
}
private enum State { private enum State {
INITIAL(R.string.tools_installer_initial, true), INITIAL(R.string.tools_installer_initial, true),
ALREADY(R.string.tools_installer_already, false), ALREADY(R.string.tools_installer_already, false),
FAILURE(R.string.tools_installer_failure, true), FAILURE(R.string.tools_installer_failure, true),
WORKING(R.string.tools_installer_working, false),
INITIAL_SYSTEM(R.string.tools_installer_initial_system, true), INITIAL_SYSTEM(R.string.tools_installer_initial_system, true),
SUCCESS_SYSTEM(R.string.tools_installer_success_system, false), SUCCESS_SYSTEM(R.string.tools_installer_success_system, false),
WORKING_SYSTEM(R.string.tools_installer_working_system, false),
INITIAL_MAGISK(R.string.tools_installer_initial_magisk, true), INITIAL_MAGISK(R.string.tools_installer_initial_magisk, true),
SUCCESS_MAGISK(R.string.tools_installer_success_magisk, false), SUCCESS_MAGISK(R.string.tools_installer_success_magisk, false);
WORKING_MAGISK(R.string.tools_installer_working_magisk, false);
private final int messageResourceId; private final int messageResourceId;
private final boolean shouldEnableView; private final boolean shouldEnableView;

View File

@ -25,6 +25,12 @@ import java.util.List;
*/ */
public final class ToolsInstaller { public final class ToolsInstaller {
public static final int ERROR = 0x0;
public static final int YES = 0x1;
public static final int NO = 0x2;
public static final int MAGISK = 0x4;
public static final int SYSTEM = 0x8;
private static final String[][] EXECUTABLES = { private static final String[][] EXECUTABLES = {
{"libwg.so", "wg"}, {"libwg.so", "wg"},
{"libwg-quick.so", "wg-quick"}, {"libwg-quick.so", "wg-quick"},
@ -60,9 +66,8 @@ public final class ToolsInstaller {
} }
public int areInstalled() throws NoRootException { public int areInstalled() throws NoRootException {
willInstallAsMagiskModule(true);
if (INSTALL_DIR == null) if (INSTALL_DIR == null)
return OsConstants.ENOENT; return ERROR;
final StringBuilder script = new StringBuilder(); final StringBuilder script = new StringBuilder();
for (final String[] names : EXECUTABLES) { for (final String[] names : EXECUTABLES) {
script.append(String.format("cmp -s '%s' '%s' && ", script.append(String.format("cmp -s '%s' '%s' && ",
@ -71,9 +76,13 @@ public final class ToolsInstaller {
} }
script.append("exit ").append(OsConstants.EALREADY).append(';'); script.append("exit ").append(OsConstants.EALREADY).append(';');
try { try {
return Application.getRootShell().run(null, script.toString()); final int ret = Application.getRootShell().run(null, script.toString());
if (ret == OsConstants.EALREADY)
return willInstallAsMagiskModule() ? YES | MAGISK : YES | SYSTEM;
else
return willInstallAsMagiskModule() ? NO | MAGISK : NO | SYSTEM;
} catch (final IOException ignored) { } catch (final IOException ignored) {
return OsConstants.EXIT_FAILURE; return ERROR;
} }
} }
@ -97,11 +106,9 @@ public final class ToolsInstaller {
} }
} }
public boolean willInstallAsMagiskModule(boolean checkForIt) { private boolean willInstallAsMagiskModule() {
synchronized (lock) { synchronized (lock) {
if (installAsMagiskModule == null) { if (installAsMagiskModule == null) {
if (!checkForIt)
throw new RuntimeException("Expected to already know whether this is a Magisk system");
try { try {
installAsMagiskModule = Application.getRootShell().run(null, "[ -d /sbin/.core/mirror -a -d /sbin/.core/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS; installAsMagiskModule = Application.getRootShell().run(null, "[ -d /sbin/.core/mirror -a -d /sbin/.core/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS;
} catch (final Exception ignored) { } catch (final Exception ignored) {
@ -123,9 +130,9 @@ public final class ToolsInstaller {
new File(nativeLibraryDir, names[0]), destination, destination, destination)); new File(nativeLibraryDir, names[0]), destination, destination, destination));
} }
try { try {
return Application.getRootShell().run(null, script.toString()); return Application.getRootShell().run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR;
} catch (final IOException ignored) { } catch (final IOException ignored) {
return OsConstants.EXIT_FAILURE; return ERROR;
} }
} }
@ -144,14 +151,14 @@ public final class ToolsInstaller {
script.append("trap - INT TERM EXIT;"); script.append("trap - INT TERM EXIT;");
try { try {
return Application.getRootShell().run(null, script.toString()); return Application.getRootShell().run(null, script.toString()) == 0 ? YES | MAGISK : ERROR;
} catch (final IOException ignored) { } catch (final IOException ignored) {
return OsConstants.EXIT_FAILURE; return ERROR;
} }
} }
public int install() throws NoRootException { public int install() throws NoRootException {
return willInstallAsMagiskModule(true) ? installMagisk() : installSystem(); return willInstallAsMagiskModule() ? installMagisk() : installSystem();
} }
public int symlink() throws NoRootException { public int symlink() throws NoRootException {

View File

@ -70,13 +70,12 @@
<string name="tools_installer_already">wg and wg-quick are already installed</string> <string name="tools_installer_already">wg and wg-quick are already installed</string>
<string name="tools_installer_failure">Unable to install command-line tools (no root?)</string> <string name="tools_installer_failure">Unable to install command-line tools (no root?)</string>
<string name="tools_installer_initial">Install optional tools for scripting</string> <string name="tools_installer_initial">Install optional tools for scripting</string>
<string name="tools_installer_working">Installing wg and wg-quick</string>
<string name="tools_installer_initial_system">Install optional tools for scripting into the system partition</string> <string name="tools_installer_initial_system">Install optional tools for scripting into the system partition</string>
<string name="tools_installer_initial_magisk">Install optional tools for scripting as Magisk module</string> <string name="tools_installer_initial_magisk">Install optional tools for scripting as Magisk module</string>
<string name="tools_installer_success_system">wg and wg-quick installed into the system partition</string> <string name="tools_installer_success_system">wg and wg-quick installed into the system partition</string>
<string name="tools_installer_success_magisk">wg and wg-quick installed as a Magisk module (reboot required)</string> <string name="tools_installer_success_magisk">wg and wg-quick installed as a Magisk module (reboot required)</string>
<string name="tools_installer_title">Install command line tools</string> <string name="tools_installer_title">Install command line tools</string>
<string name="tools_installer_working_system">Installing wg and wg-quick into the system partition</string>
<string name="tools_installer_working_magisk">Installing wg and wg-quick as a Magisk module</string>
<string name="tunnel_create_error">Unable to create tunnel: %s</string> <string name="tunnel_create_error">Unable to create tunnel: %s</string>
<string name="tunnel_create_success">Successfully created tunnel “%s”</string> <string name="tunnel_create_success">Successfully created tunnel “%s”</string>
<string name="tunnel_rename_error">Unable to rename tunnel: %s</string> <string name="tunnel_rename_error">Unable to rename tunnel: %s</string>