diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..dd370c8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libraries/libsuperuser"] + path = libraries/libsuperuser + url = https://github.com/dschuermann/libsuperuser.git diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f8af12 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +## Dependencies + +The RootCommands library (licensed under Apache 2.0) is used for root access, and included in the `libraries/` folder. \ No newline at end of file diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..ef32e5b --- /dev/null +++ b/app/app.iml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build.gradle b/app/build.gradle index 1a236e8..d04cbbf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'android' - android { compileSdkVersion 19 buildToolsVersion '19.1.0' @@ -9,9 +8,13 @@ android { targetSdkVersion 19 versionCode 1 versionName '0.1.0' + testApplicationId 'com.nutomic.zertman.test' + testInstrumentationRunner 'android.test.InstrumentationTestRunner' + testHandleProfiling true + testFunctionalTest true } } dependencies { - compile project (':libraries:RootCommands') -} + compile project(':libsuperuser') +} \ No newline at end of file diff --git a/app/src/androidTest/java/com.nutomic.zertman.test/CertificateManagerTest.java b/app/src/androidTest/java/com.nutomic.zertman.test/CertificateManagerTest.java new file mode 100644 index 0000000..3507c30 --- /dev/null +++ b/app/src/androidTest/java/com.nutomic.zertman.test/CertificateManagerTest.java @@ -0,0 +1,125 @@ +package com.nutomic.zertman.test; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; + +import com.nutomic.zertman.Certificate; +import com.nutomic.zertman.CertificateManager; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import eu.chainfire.libsuperuser.Shell; + +public class CertificateManagerTest extends AndroidTestCase { + + /** + * Android (4.4) seems to name certificates like "5ed36f99.0", so using something + * different from that scheme should avoid collisions with real certificates. + */ + private static final String TEST_CERTIFICATE_NAME = "test_certificate"; + + private CertificateManager mCertificateManager; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mCertificateManager = new CertificateManager(); + + assertTrue(Shell.SU.available()); + } + + @SmallTest + public void testUserCertificates() { + Certificate cert = copyCertificate(false); + assertTrue(mCertificateManager.getCertificates(false).contains(cert)); + assertTrue(mCertificateManager.deleteCertificate(cert)); + assertReadOnly(); + } + + @MediumTest + public void testMoveCertificate() { + Certificate cert = copyCertificate(false); + Certificate newCertificate = mCertificateManager.moveCertificateToSystem(cert); + assertReadOnly(); + assertNotNull(newCertificate); + assertEquals(newCertificate.getFile().getName(), cert.getFile().getName()); + assertNotSame(newCertificate.getFile(), cert.getFile()); + assertFalse(mCertificateManager.getCertificates(false).contains(cert)); + assertTrue(mCertificateManager.getCertificates(true).contains(newCertificate)); + assertTrue(mCertificateManager.deleteCertificate(newCertificate)); + assertReadOnly(); + } + + @SmallTest + public void testSystemCertificates() { + Certificate cert = copyCertificate(true); + assertTrue(mCertificateManager.getCertificates(true).contains(cert)); + assertTrue(mCertificateManager.deleteCertificate(cert)); + assertReadOnly(); + } + + /** + * Checks that /system is properly remounted as read only. + */ + private void assertReadOnly() { + List mount = Shell.SU.run("mount | grep system"); + assertTrue(mount.get(0).contains("ro")); + assertFalse(mount.get(0).contains("rw")); + } + + /** + * Copies test certificate from resources to user or system certificates. + * + * This should always work, but seems to fail randomly. + */ + private Certificate copyCertificate(boolean isSystemCertificate) { + FileOutputStream tempFileStream = null; + InputStream resourceStream = null; + File source = null; + try { + resourceStream = getContext().getResources().openRawResource(R.raw.test_certificate); + source = new File(getContext().getCacheDir(), "zertman-test.tmp"); + source.createNewFile(); + tempFileStream = new FileOutputStream(source); + byte[] buff = new byte[1024]; + int read; + while ((read = resourceStream.read(buff)) > 0) { + tempFileStream.write(buff, 0, read); + } + assertNotNull(Shell.SU.run( + "mv " + source.getAbsolutePath() + " " + + CertificateManager.USER_CERTIFICATES_DIR + "/" + TEST_CERTIFICATE_NAME)); + // NOTE: We use CertificateManager.moveCertificateToSystem() to avoid + // implementing system remount again. + return (isSystemCertificate) + ? mCertificateManager.moveCertificateToSystem( + new Certificate(TEST_CERTIFICATE_NAME, false)) + : new Certificate(TEST_CERTIFICATE_NAME, isSystemCertificate); + } + catch (FileNotFoundException e) { + fail(); + } + catch (IOException e) { + fail(); + } + finally { + source.delete(); + try { + resourceStream.close(); + tempFileStream.close(); + } + catch (IOException e) { + fail(); + } + } + return null; + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com.nutomic.zertman.test/CertificateTest.java b/app/src/androidTest/java/com.nutomic.zertman.test/CertificateTest.java new file mode 100644 index 0000000..1bb78c9 --- /dev/null +++ b/app/src/androidTest/java/com.nutomic.zertman.test/CertificateTest.java @@ -0,0 +1,29 @@ +package com.nutomic.zertman.test; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; + +import com.nutomic.zertman.Certificate; + +public class CertificateTest extends AndroidTestCase { + + Certificate a1 = new Certificate("a", true); + Certificate a2 = new Certificate("a", false); + Certificate b1 = new Certificate("b", true); + Certificate b2 = new Certificate("b", false); + + @SmallTest + public void testEquals() { + assertNotSame(a1, a2); + assertNotSame(a2, b1); + assertNotSame(a1, b1); + assertNotSame(a2, b2); + } + + @SmallTest + public void testGetFile() { + assertNotNull(a1.getFile()); + assertNotNull(a2.getFile()); + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com.nutomic.zertman.test/MainActivityTest.java b/app/src/androidTest/java/com.nutomic.zertman.test/MainActivityTest.java new file mode 100644 index 0000000..67884e9 --- /dev/null +++ b/app/src/androidTest/java/com.nutomic.zertman.test/MainActivityTest.java @@ -0,0 +1,19 @@ +package com.nutomic.zertman.test; + +import android.test.ActivityUnitTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.nutomic.zertman.MainActivity; + +public class MainActivityTest extends ActivityUnitTestCase { + + public MainActivityTest() { + super(MainActivity.class); + } + + @SmallTest + public void testBlah() { + // TODO: test for showing ListView with all cert files + } + +} \ No newline at end of file diff --git a/app/src/androidTest/res/raw/test_certificate b/app/src/androidTest/res/raw/test_certificate new file mode 100644 index 0000000..719b0ff Binary files /dev/null and b/app/src/androidTest/res/raw/test_certificate differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e77ee5a..f42cc01 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + getCertificates(boolean system) { + String[] list = (system) + ? SYSTEM_CERTIFICATES_DIR.list() + : USER_CERTIFICATES_DIR.list(); + ArrayList ret = new ArrayList(); + for (String file : list) { + ret.add(new Certificate(file, system)); + } + return ret; + } + + /** + * Moves the specified certificate from user storage to system storage. + * + * @return The updated certificate (located in system storage). + */ + public Certificate moveCertificateToSystem(Certificate certificate) { + remountSystem(Mode.ReadWrite); + // NOTE: Using mv gives error: "failed on *file* - Cross-device link". + run("cp " + USER_CERTIFICATES_DIR + "/" + certificate.getFile().getName() + + " " + SYSTEM_CERTIFICATES_DIR + "/" + certificate.getFile().getName()); + remountSystem(Mode.ReadOnly); + deleteCertificate(certificate); + return new Certificate(certificate.getFile().getName(), true); + } + + /** + * Deletes the given certificate from storage. + * + * @return True on success. + */ + public boolean deleteCertificate(Certificate certificate) { + remountSystem(Mode.ReadWrite); + boolean success = run("rm " + certificate.getFile().getAbsolutePath()); + remountSystem(Mode.ReadOnly); + return success; + } + + /** + * Remounts the /system partition as read write or read only. + */ + private void remountSystem(Mode mode) { + run((mode == Mode.ReadOnly) + ? "mount -o ro,remount /system" + : "mount -o rw,remount /system"); + } + + /** + * Runs the given command as root and logs any errors. + * + * @param command The command to execute. + * @return True on success. + */ + private boolean run(String command) { + List result = Shell.SU.run(command); + if (result == null) { + Log.w(TAG, "Failed to execute root command: " + command); + } + return result != null; + } + +} diff --git a/app/src/main/java/com/nutomic/zertman/MainActivity.java b/app/src/main/java/com/nutomic/zertman/MainActivity.java index c4a7617..ae8715e 100644 --- a/app/src/main/java/com/nutomic/zertman/MainActivity.java +++ b/app/src/main/java/com/nutomic/zertman/MainActivity.java @@ -2,10 +2,6 @@ package com.nutomic.zertman; import android.app.Activity; import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; - -import org.sufficientlysecure.rootcommands.command.SimpleCommand; public class MainActivity extends Activity { diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/build.gradle b/build.gradle index aadd3db..e90813a 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:0.11.+' + classpath 'com.android.tools.build:gradle:0.12.+' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -16,4 +16,4 @@ allprojects { repositories { mavenCentral() } -} +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5de946b..0af6e3f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Tue Jul 15 18:59:06 CEST 2014 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip diff --git a/libraries/RootCommands/.gitignore b/libraries/RootCommands/.gitignore deleted file mode 100644 index afa9dfc..0000000 --- a/libraries/RootCommands/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -#Android specific -bin -gen -obj -libs/armeabi -lint.xml -local.properties -release.properties -ant.properties -*.class -*.apk - -#Gradle -.gradle -build -gradle.properties -gradlew -gradlew.bat -gradle - -#Maven -target -pom.xml.* - -#Eclipse -.project -.classpath -.settings -.metadata - -#IntelliJ IDEA -.idea -*.iml diff --git a/libraries/RootCommands/build.gradle b/libraries/RootCommands/build.gradle deleted file mode 100644 index 3b90c97..0000000 --- a/libraries/RootCommands/build.gradle +++ /dev/null @@ -1,24 +0,0 @@ -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:0.5.+' - } -} - -apply plugin: 'android-library' - -dependencies { -} - -android { - compileSdkVersion 17 - buildToolsVersion "19.1.0" - - defaultConfig { - minSdkVersion 7 - targetSdkVersion 17 - } -} - diff --git a/libraries/RootCommands/src/main/AndroidManifest.xml b/libraries/RootCommands/src/main/AndroidManifest.xml deleted file mode 100644 index ee9b199..0000000 --- a/libraries/RootCommands/src/main/AndroidManifest.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Mount.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Mount.java deleted file mode 100644 index 6f5ef78..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Mount.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks (RootTools) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands; - -import java.io.File; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -public class Mount { - protected final File mDevice; - protected final File mMountPoint; - protected final String mType; - protected final Set mFlags; - - Mount(File device, File path, String type, String flagsStr) { - mDevice = device; - mMountPoint = path; - mType = type; - mFlags = new HashSet(Arrays.asList(flagsStr.split(","))); - } - - public File getDevice() { - return mDevice; - } - - public File getMountPoint() { - return mMountPoint; - } - - public String getType() { - return mType; - } - - public Set getFlags() { - return mFlags; - } - - @Override - public String toString() { - return String.format("%s on %s type %s %s", mDevice, mMountPoint, mType, mFlags); - } -} diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Remounter.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Remounter.java deleted file mode 100644 index 00d4e2c..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Remounter.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks (RootTools) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.LineNumberReader; -import java.util.ArrayList; -import java.util.Locale; - -import org.sufficientlysecure.rootcommands.command.SimpleCommand; -import org.sufficientlysecure.rootcommands.util.Log; - -//no modifier, this means it is package-private. Only our internal classes can use this. -class Remounter { - - private Shell shell; - - public Remounter(Shell shell) { - super(); - this.shell = shell; - } - - /** - * This will take a path, which can contain the file name as well, and attempt to remount the - * underlying partition. - *

- * For example, passing in the following string: - * "/system/bin/some/directory/that/really/would/never/exist" will result in /system ultimately - * being remounted. However, keep in mind that the longer the path you supply, the more work - * this has to do, and the slower it will run. - * - * @param file - * file path - * @param mountType - * mount type: pass in RO (Read only) or RW (Read Write) - * @return a boolean which indicates whether or not the partition has been - * remounted as specified. - */ - protected boolean remount(String file, String mountType) { - - // if the path has a trailing slash get rid of it. - if (file.endsWith("/") && !file.equals("/")) { - file = file.substring(0, file.lastIndexOf("/")); - } - // Make sure that what we are trying to remount is in the mount list. - boolean foundMount = false; - while (!foundMount) { - try { - for (Mount mount : getMounts()) { - Log.d(RootCommands.TAG, mount.getMountPoint().toString()); - - if (file.equals(mount.getMountPoint().toString())) { - foundMount = true; - break; - } - } - } catch (Exception e) { - Log.e(RootCommands.TAG, "Exception", e); - return false; - } - if (!foundMount) { - try { - file = (new File(file).getParent()).toString(); - } catch (Exception e) { - Log.e(RootCommands.TAG, "Exception", e); - return false; - } - } - } - Mount mountPoint = findMountPointRecursive(file); - - Log.d(RootCommands.TAG, "Remounting " + mountPoint.getMountPoint().getAbsolutePath() - + " as " + mountType.toLowerCase(Locale.US)); - final boolean isMountMode = mountPoint.getFlags().contains(mountType.toLowerCase(Locale.US)); - - if (!isMountMode) { - // grab an instance of the internal class - try { - SimpleCommand command = new SimpleCommand("busybox mount -o remount," - + mountType.toLowerCase(Locale.US) + " " + mountPoint.getDevice().getAbsolutePath() - + " " + mountPoint.getMountPoint().getAbsolutePath(), - "toolbox mount -o remount," + mountType.toLowerCase(Locale.US) + " " - + mountPoint.getDevice().getAbsolutePath() + " " - + mountPoint.getMountPoint().getAbsolutePath(), "mount -o remount," - + mountType.toLowerCase(Locale.US) + " " - + mountPoint.getDevice().getAbsolutePath() + " " - + mountPoint.getMountPoint().getAbsolutePath(), - "/system/bin/toolbox mount -o remount," + mountType.toLowerCase(Locale.US) + " " - + mountPoint.getDevice().getAbsolutePath() + " " - + mountPoint.getMountPoint().getAbsolutePath()); - - // execute on shell - shell.add(command).waitForFinish(); - - } catch (Exception e) { - } - - mountPoint = findMountPointRecursive(file); - } - - if (mountPoint != null) { - Log.d(RootCommands.TAG, mountPoint.getFlags() + " AND " + mountType.toLowerCase(Locale.US)); - if (mountPoint.getFlags().contains(mountType.toLowerCase(Locale.US))) { - Log.d(RootCommands.TAG, mountPoint.getFlags().toString()); - return true; - } else { - Log.d(RootCommands.TAG, mountPoint.getFlags().toString()); - } - } else { - Log.d(RootCommands.TAG, "mountPoint is null"); - } - return false; - } - - private Mount findMountPointRecursive(String file) { - try { - ArrayList mounts = getMounts(); - for (File path = new File(file); path != null;) { - for (Mount mount : mounts) { - if (mount.getMountPoint().equals(path)) { - return mount; - } - } - } - return null; - } catch (IOException e) { - throw new RuntimeException(e); - } catch (Exception e) { - Log.e(RootCommands.TAG, "Exception", e); - } - return null; - } - - /** - * This will return an ArrayList of the class Mount. The class mount contains the following - * property's: device mountPoint type flags - *

- * These will provide you with any information you need to work with the mount points. - * - * @return ArrayList an ArrayList of the class Mount. - * @throws Exception - * if we cannot return the mount points. - */ - protected static ArrayList getMounts() throws Exception { - - final String tempFile = "/data/local/RootToolsMounts"; - - // copy /proc/mounts to tempfile. Directly reading it does not work on 4.3 - Shell shell = Shell.startRootShell(); - Toolbox tb = new Toolbox(shell); - tb.copyFile("/proc/mounts", tempFile, false, false); - tb.setFilePermissions(tempFile, "777"); - shell.close(); - - LineNumberReader lnr = null; - lnr = new LineNumberReader(new FileReader(tempFile)); - String line; - ArrayList mounts = new ArrayList(); - while ((line = lnr.readLine()) != null) { - - Log.d(RootCommands.TAG, line); - - String[] fields = line.split(" "); - mounts.add(new Mount(new File(fields[0]), // device - new File(fields[1]), // mountPoint - fields[2], // fstype - fields[3] // flags - )); - } - lnr.close(); - - return mounts; - } -} diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/RootCommands.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/RootCommands.java deleted file mode 100644 index 2557628..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/RootCommands.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands; - -import org.sufficientlysecure.rootcommands.util.Log; - -public class RootCommands { - public static boolean DEBUG = false; - public static int DEFAULT_TIMEOUT = 10000; - - public static final String TAG = "RootCommands"; - - /** - * General method to check if user has su binary and accepts root access for this program! - * - * @return true if everything worked - */ - public static boolean rootAccessGiven() { - boolean rootAccess = false; - - try { - Shell rootShell = Shell.startRootShell(); - - Toolbox tb = new Toolbox(rootShell); - if (tb.isRootAccessGiven()) { - rootAccess = true; - } - - rootShell.close(); - } catch (Exception e) { - Log.e(TAG, "Problem while checking for root access!", e); - } - - return rootAccess; - } -} diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Shell.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Shell.java deleted file mode 100644 index f3f0dd9..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Shell.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks, Jeremy Lakeman (RootTools) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands; - -import java.io.BufferedReader; -import java.io.Closeable; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; - -import org.sufficientlysecure.rootcommands.command.Command; -import org.sufficientlysecure.rootcommands.util.Log; -import org.sufficientlysecure.rootcommands.util.RootAccessDeniedException; -import org.sufficientlysecure.rootcommands.util.Utils; - -public class Shell implements Closeable { - private final Process shellProcess; - private final BufferedReader stdOutErr; - private final DataOutputStream outputStream; - private final List commands = new ArrayList(); - private boolean close = false; - - private static final String LD_LIBRARY_PATH = System.getenv("LD_LIBRARY_PATH"); - private static final String token = "F*D^W@#FGF"; - - /** - * Start root shell - * - * @param customEnv - * @param baseDirectory - * @return - * @throws IOException - */ - public static Shell startRootShell(ArrayList customEnv, String baseDirectory) - throws IOException, RootAccessDeniedException { - Log.d(RootCommands.TAG, "Starting Root Shell!"); - - // On some versions of Android (ICS) LD_LIBRARY_PATH is unset when using su - // We need to pass LD_LIBRARY_PATH over su for some commands to work correctly. - if (customEnv == null) { - customEnv = new ArrayList(); - } - customEnv.add("LD_LIBRARY_PATH=" + LD_LIBRARY_PATH); - - Shell shell = new Shell(Utils.getSuPath(), customEnv, baseDirectory); - - return shell; - } - - /** - * Start root shell without custom environment and base directory - * - * @return - * @throws IOException - */ - public static Shell startRootShell() throws IOException, RootAccessDeniedException { - return startRootShell(null, null); - } - - /** - * Start default sh shell - * - * @param customEnv - * @param baseDirectory - * @return - * @throws IOException - */ - public static Shell startShell(ArrayList customEnv, String baseDirectory) - throws IOException { - Log.d(RootCommands.TAG, "Starting Shell!"); - Shell shell = new Shell("sh", customEnv, baseDirectory); - return shell; - } - - /** - * Start default sh shell without custom environment and base directory - * - * @return - * @throws IOException - */ - public static Shell startShell() throws IOException { - return startShell(null, null); - } - - /** - * Start custom shell defined by shellPath - * - * @param shellPath - * @param customEnv - * @param baseDirectory - * @return - * @throws IOException - */ - public static Shell startCustomShell(String shellPath, ArrayList customEnv, - String baseDirectory) throws IOException { - Log.d(RootCommands.TAG, "Starting Custom Shell!"); - Shell shell = new Shell(shellPath, customEnv, baseDirectory); - - return shell; - } - - /** - * Start custom shell without custom environment and base directory - * - * @param shellPath - * @return - * @throws IOException - */ - public static Shell startCustomShell(String shellPath) throws IOException { - return startCustomShell(shellPath, null, null); - } - - private Shell(String shell, ArrayList customEnv, String baseDirectory) - throws IOException, RootAccessDeniedException { - Log.d(RootCommands.TAG, "Starting shell: " + shell); - - // start shell process! - shellProcess = Utils.runWithEnv(shell, customEnv, baseDirectory); - - // StdErr is redirected to StdOut, defined in Command.getCommand() - stdOutErr = new BufferedReader(new InputStreamReader(shellProcess.getInputStream())); - outputStream = new DataOutputStream(shellProcess.getOutputStream()); - - outputStream.write("echo Started\n".getBytes()); - outputStream.flush(); - - while (true) { - String line = stdOutErr.readLine(); - if (line == null) - throw new RootAccessDeniedException( - "stdout line is null! Access was denied or this executeable is not a shell!"); - if ("".equals(line)) - continue; - if ("Started".equals(line)) - break; - - destroyShellProcess(); - throw new IOException("Unable to start shell, unexpected output \"" + line + "\""); - } - - new Thread(inputRunnable, "Shell Input").start(); - new Thread(outputRunnable, "Shell Output").start(); - } - - private Runnable inputRunnable = new Runnable() { - public void run() { - try { - writeCommands(); - } catch (IOException e) { - Log.e(RootCommands.TAG, "IO Exception", e); - } - } - }; - - private Runnable outputRunnable = new Runnable() { - public void run() { - try { - readOutput(); - } catch (IOException e) { - Log.e(RootCommands.TAG, "IOException", e); - } catch (InterruptedException e) { - Log.e(RootCommands.TAG, "InterruptedException", e); - } - } - }; - - /** - * Destroy shell process considering that the process could already be terminated - */ - private void destroyShellProcess() { - try { - // Yes, this really is the way to check if the process is - // still running. - shellProcess.exitValue(); - } catch (IllegalThreadStateException e) { - // Only call destroy() if the process is still running; - // Calling it for a terminated process will not crash, but - // (starting with at least ICS/4.0) spam the log with INFO - // messages ala "Failed to destroy process" and "kill - // failed: ESRCH (No such process)". - shellProcess.destroy(); - } - - Log.d(RootCommands.TAG, "Shell destroyed"); - } - - /** - * Writes queued commands one after another into the opened shell. After an execution a token is - * written to seperate command output on read - * - * @throws IOException - */ - private void writeCommands() throws IOException { - try { - int commandIndex = 0; - while (true) { - DataOutputStream out; - synchronized (commands) { - while (!close && commandIndex >= commands.size()) { - commands.wait(); - } - out = this.outputStream; - } - if (commandIndex < commands.size()) { - Command next = commands.get(commandIndex); - next.writeCommand(out); - String line = "\necho " + token + " " + commandIndex + " $?\n"; - out.write(line.getBytes()); - out.flush(); - commandIndex++; - } else if (close) { - out.write("\nexit 0\n".getBytes()); - out.flush(); - out.close(); - Log.d(RootCommands.TAG, "Closing shell"); - return; - } - } - } catch (InterruptedException e) { - Log.e(RootCommands.TAG, "interrupted while writing command", e); - } - } - - /** - * Reads output line by line, seperated by token written after every command - * - * @throws IOException - * @throws InterruptedException - */ - private void readOutput() throws IOException, InterruptedException { - Command command = null; - - // index of current command - int commandIndex = 0; - while (true) { - String lineStdOut = stdOutErr.readLine(); - - // terminate on EOF - if (lineStdOut == null) - break; - - if (command == null) { - - // break on close after last command - if (commandIndex >= commands.size()) { - if (close) - break; - continue; - } - - // get current command - command = commands.get(commandIndex); - } - - int pos = lineStdOut.indexOf(token); - if (pos > 0) { - command.processOutput(lineStdOut.substring(0, pos)); - } - if (pos >= 0) { - lineStdOut = lineStdOut.substring(pos); - String fields[] = lineStdOut.split(" "); - int id = Integer.parseInt(fields[1]); - if (id == commandIndex) { - command.setExitCode(Integer.parseInt(fields[2])); - - // go to next command - commandIndex++; - command = null; - continue; - } - } - command.processOutput(lineStdOut); - } - Log.d(RootCommands.TAG, "Read all output"); - shellProcess.waitFor(); - destroyShellProcess(); - - while (commandIndex < commands.size()) { - if (command == null) { - command = commands.get(commandIndex); - } - command.terminated("Unexpected Termination!"); - commandIndex++; - command = null; - } - } - - /** - * Add command to shell queue - * - * @param command - * @return - * @throws IOException - */ - public Command add(Command command) throws IOException { - if (close) - throw new IOException("Unable to add commands to a closed shell"); - synchronized (commands) { - commands.add(command); - // set shell on the command object, to know where the command is running on - command.addedToShell(this, (commands.size() - 1)); - commands.notifyAll(); - } - - return command; - } - - /** - * Close shell - * - * @throws IOException - */ - public void close() throws IOException { - synchronized (commands) { - this.close = true; - commands.notifyAll(); - } - } - - /** - * Returns number of queued commands - * - * @return - */ - public int getCommandsSize() { - return commands.size(); - } - -} \ No newline at end of file diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/SystemCommands.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/SystemCommands.java deleted file mode 100644 index 355e695..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/SystemCommands.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands; - -import android.annotation.TargetApi; -import android.content.ContentResolver; -import android.content.Context; -import android.location.LocationManager; -import android.os.PowerManager; -import android.provider.Settings; - -/** - * This methods work when the apk is installed as a system app (under /system/app) - */ -public class SystemCommands { - Context context; - - public SystemCommands(Context context) { - super(); - this.context = context; - } - - /** - * Get GPS status - * - * @return - */ - public boolean getGPS() { - return ((LocationManager) context.getSystemService(Context.LOCATION_SERVICE)) - .isProviderEnabled(LocationManager.GPS_PROVIDER); - } - - /** - * Enable/Disable GPS - * - * @param value - */ - @TargetApi(8) - public void setGPS(boolean value) { - ContentResolver localContentResolver = context.getContentResolver(); - Settings.Secure.setLocationProviderEnabled(localContentResolver, - LocationManager.GPS_PROVIDER, value); - } - - /** - * TODO: Not ready yet - */ - @TargetApi(8) - public void reboot() { - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - pm.reboot("recovery"); - pm.reboot(null); - - // not working: - // reboot(null); - } - - /** - * Reboot the device immediately, passing 'reason' (may be null) to the underlying __reboot - * system call. Should not return. - * - * Taken from com.android.server.PowerManagerService.reboot - */ - // public void reboot(String reason) { - // - // // final String finalReason = reason; - // Runnable runnable = new Runnable() { - // public void run() { - // synchronized (this) { - // // ShutdownThread.reboot(mContext, finalReason, false); - // try { - // Class clazz = Class.forName("com.android.internal.app.ShutdownThread"); - // - // // if (mReboot) { - // Method method = clazz.getMethod("reboot", Context.class, String.class, - // Boolean.TYPE); - // method.invoke(null, context, null, false); - // - // // if (mReboot) { - // // Method method = clazz.getMethod("reboot", Context.class, String.class, - // // Boolean.TYPE); - // // method.invoke(null, mContext, mReason, mConfirm); - // // } else { - // // Method method = clazz.getMethod("shutdown", Context.class, Boolean.TYPE); - // // method.invoke(null, mContext, mConfirm); - // // } - // } catch (Exception e) { - // e.printStackTrace(); - // } - // } - // - // } - // }; - // // ShutdownThread must run on a looper capable of displaying the UI. - // mHandler.post(runnable); - // - // // PowerManager.reboot() is documented not to return so just wait for the inevitable. - // // synchronized (runnable) { - // // while (true) { - // // try { - // // runnable.wait(); - // // } catch (InterruptedException e) { - // // } - // // } - // // } - // } - -} diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Toolbox.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Toolbox.java deleted file mode 100644 index fe8d2b4..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Toolbox.java +++ /dev/null @@ -1,824 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks (RootTools) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.concurrent.TimeoutException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.sufficientlysecure.rootcommands.command.ExecutableCommand; -import org.sufficientlysecure.rootcommands.command.Command; -import org.sufficientlysecure.rootcommands.command.SimpleCommand; -import org.sufficientlysecure.rootcommands.util.BrokenBusyboxException; -import org.sufficientlysecure.rootcommands.util.Log; - -import android.os.StatFs; -import android.os.SystemClock; - -/** - * All methods in this class are working with Androids toolbox. Toolbox is similar to busybox, but - * normally shipped on every Android OS. You can find toolbox commands on - * https://github.com/CyanogenMod/android_system_core/tree/ics/toolbox - * - * This means that these commands are designed to work on every Android OS, with a _working_ toolbox - * binary on it. They don't require busybox! - * - */ -public class Toolbox { - private Shell shell; - - /** - * All methods in this class are working with Androids toolbox. Toolbox is similar to busybox, - * but normally shipped on every Android OS. - * - * @param shell - * where to execute commands on - */ - public Toolbox(Shell shell) { - super(); - this.shell = shell; - } - - /** - * Checks if user accepted root access - * - * (commands: id) - * - * @return true if user has given root access - * @throws IOException - * @throws TimeoutException - * @throws BrokenBusyboxException - */ - public boolean isRootAccessGiven() throws BrokenBusyboxException, TimeoutException, IOException { - SimpleCommand idCommand = new SimpleCommand("id"); - shell.add(idCommand).waitForFinish(); - - if (idCommand.getOutput().contains("uid=0")) { - return true; - } else { - return false; - } - } - - /** - * This command class gets all pids to a given process name - */ - private class PsCommand extends Command { - private String processName; - private ArrayList pids; - private String psRegex; - private Pattern psPattern; - - public PsCommand(String processName) { - super("ps"); - this.processName = processName; - pids = new ArrayList(); - - /** - * regex to get pid out of ps line, example: - * - *

-             *  root    24736    1   12140  584   ffffffff 40010d14 S /data/data/org.adaway/files/blank_webserver
-             * ^\\S \\s ([0-9]+)                          .*                                      processName    $
-             * 
- */ - psRegex = "^\\S+\\s+([0-9]+).*" + Pattern.quote(processName) + "$"; - psPattern = Pattern.compile(psRegex); - } - - public ArrayList getPids() { - return pids; - } - - public String getPidsString() { - StringBuilder sb = new StringBuilder(); - for (String s : pids) { - sb.append(s); - sb.append(" "); - } - - return sb.toString(); - } - - @Override - public void output(int id, String line) { - // general check if line contains processName - if (line.contains(processName)) { - Matcher psMatcher = psPattern.matcher(line); - - // try to match line exactly - try { - if (psMatcher.find()) { - String pid = psMatcher.group(1); - // add to pids list - pids.add(pid); - Log.d(RootCommands.TAG, "Found pid: " + pid); - } else { - Log.d(RootCommands.TAG, "Matching in ps command failed!"); - } - } catch (Exception e) { - Log.e(RootCommands.TAG, "Error with regex!", e); - } - } - } - - @Override - public void afterExecution(int id, int exitCode) { - } - - } - - /** - * This method can be used to kill a running process - * - * (commands: ps, kill) - * - * @param processName - * name of process to kill - * @return true if process was found and killed successfully - * @throws IOException - * @throws TimeoutException - * @throws BrokenBusyboxException - */ - public boolean killAll(String processName) throws BrokenBusyboxException, TimeoutException, - IOException { - Log.d(RootCommands.TAG, "Killing process " + processName); - - PsCommand psCommand = new PsCommand(processName); - shell.add(psCommand).waitForFinish(); - - // kill processes - if (!psCommand.getPids().isEmpty()) { - // example: kill -9 1234 1222 5343 - SimpleCommand killCommand = new SimpleCommand("kill -9 " - + psCommand.getPidsString()); - shell.add(killCommand).waitForFinish(); - - if (killCommand.getExitCode() == 0) { - return true; - } else { - return false; - } - } else { - Log.d(RootCommands.TAG, "No pid found! Nothing was killed!"); - return false; - } - } - - /** - * Kill a running executable - * - * See README for more information how to use your own executables! - * - * @param executableName - * @return - * @throws BrokenBusyboxException - * @throws TimeoutException - * @throws IOException - */ - public boolean killAllExecutable(String executableName) throws BrokenBusyboxException, - TimeoutException, IOException { - return killAll(ExecutableCommand.EXECUTABLE_PREFIX + executableName + ExecutableCommand.EXECUTABLE_SUFFIX); - } - - /** - * This method can be used to to check if a process is running - * - * @param processName - * name of process to check - * @return true if process was found - * @throws IOException - * @throws BrokenBusyboxException - * @throws TimeoutException - * (Could not determine if the process is running) - */ - public boolean isProcessRunning(String processName) throws BrokenBusyboxException, - TimeoutException, IOException { - PsCommand psCommand = new PsCommand(processName); - shell.add(psCommand).waitForFinish(); - - // if pids are available process is running! - if (!psCommand.getPids().isEmpty()) { - return true; - } else { - return false; - } - } - - /** - * Checks if binary is running - * - * @param binaryName - * @return - * @throws BrokenBusyboxException - * @throws TimeoutException - * @throws IOException - */ - public boolean isBinaryRunning(String binaryName) throws BrokenBusyboxException, - TimeoutException, IOException { - return isProcessRunning(ExecutableCommand.EXECUTABLE_PREFIX + binaryName - + ExecutableCommand.EXECUTABLE_SUFFIX); - } - - /** - * Ls command to get permissions or symlinks - */ - private class LsCommand extends Command { - private String fileName; - private String permissionRegex; - private Pattern permissionPattern; - private String symlinkRegex; - private Pattern symlinkPattern; - - private String symlink; - private String permissions; - - public String getSymlink() { - return symlink; - } - - public String getPermissions() { - return permissions; - } - - public LsCommand(String file) { - super("ls -l " + file); - - // get only filename: - this.fileName = (new File(file)).getName(); - Log.d(RootCommands.TAG, "fileName: " + fileName); - - /** - * regex to get pid out of ps line, example: - * - *
-             * with busybox:
-             *     lrwxrwxrwx     1 root root            15 Aug 13 12:14 dev/stdin -> /proc/self/fd/0
-             *     
-             * with toolbox:
-             *     lrwxrwxrwx root root            15 Aug 13 12:14 stdin -> /proc/self/fd/0
-             * 
-             * Regex:
-             * ^.*?(\\S{10})                     .*                                                  $
-             * 
- */ - permissionRegex = "^.*?(\\S{10}).*$"; - permissionPattern = Pattern.compile(permissionRegex); - - /** - * regex to get symlink - * - *
-             *     ->           /proc/self/fd/0
-             * ^.*?\\-\\> \\s+  (.*)           $
-             * 
- */ - symlinkRegex = "^.*?\\-\\>\\s+(.*)$"; - symlinkPattern = Pattern.compile(symlinkRegex); - } - - /** - * Converts permission string from ls command to numerical value. Example: -rwxrwxrwx gets - * to 777 - * - * @param permissions - * @return - */ - private String convertPermissions(String permissions) { - int owner = getGroupPermission(permissions.substring(1, 4)); - int group = getGroupPermission(permissions.substring(4, 7)); - int world = getGroupPermission(permissions.substring(7, 10)); - - return "" + owner + group + world; - } - - /** - * Calculates permission for one group - * - * @param permission - * @return value of permission string - */ - private int getGroupPermission(String permission) { - int value = 0; - - if (permission.charAt(0) == 'r') { - value += 4; - } - if (permission.charAt(1) == 'w') { - value += 2; - } - if (permission.charAt(2) == 'x') { - value += 1; - } - - return value; - } - - @Override - public void output(int id, String line) { - // general check if line contains file - if (line.contains(fileName)) { - - // try to match line exactly - try { - Matcher permissionMatcher = permissionPattern.matcher(line); - if (permissionMatcher.find()) { - permissions = convertPermissions(permissionMatcher.group(1)); - - Log.d(RootCommands.TAG, "Found permissions: " + permissions); - } else { - Log.d(RootCommands.TAG, "Permissions were not found in ls command!"); - } - - // try to parse for symlink - Matcher symlinkMatcher = symlinkPattern.matcher(line); - if (symlinkMatcher.find()) { - /* - * TODO: If symlink points to a file in the same directory the path is not - * absolute!!! - */ - symlink = symlinkMatcher.group(1); - Log.d(RootCommands.TAG, "Symlink found: " + symlink); - } else { - Log.d(RootCommands.TAG, "No symlink found!"); - } - } catch (Exception e) { - Log.e(RootCommands.TAG, "Error with regex!", e); - } - } - } - - @Override - public void afterExecution(int id, int exitCode) { - } - - } - - /** - * @param file - * String that represent the file, including the full path to the file and its name. - * @param followSymlinks - * @return File permissions as String, for example: 777, returns null on error - * @throws IOException - * @throws TimeoutException - * @throws BrokenBusyboxException - * - */ - public String getFilePermissions(String file) throws BrokenBusyboxException, TimeoutException, - IOException { - Log.d(RootCommands.TAG, "Checking permissions for " + file); - - String permissions = null; - - if (fileExists(file)) { - Log.d(RootCommands.TAG, file + " was found."); - - LsCommand lsCommand = new LsCommand(file); - shell.add(lsCommand).waitForFinish(); - - permissions = lsCommand.getPermissions(); - } - - return permissions; - } - - /** - * Sets permission of file - * - * @param file - * absolute path to file - * @param permissions - * String like 777 - * @return true if command worked - * @throws BrokenBusyboxException - * @throws TimeoutException - * @throws IOException - */ - public boolean setFilePermissions(String file, String permissions) - throws BrokenBusyboxException, TimeoutException, IOException { - Log.d(RootCommands.TAG, "Set permissions of " + file + " to " + permissions); - - SimpleCommand chmodCommand = new SimpleCommand("chmod " + permissions + " " + file); - shell.add(chmodCommand).waitForFinish(); - - if (chmodCommand.getExitCode() == 0) { - return true; - } else { - return false; - } - } - - /** - * This will return a String that represent the symlink for a specified file. - * - * @param file - * The path to the file to get the Symlink for. (must have absolute path) - * - * @return A String that represent the symlink for a specified file or null if no symlink - * exists. - * @throws IOException - * @throws TimeoutException - * @throws BrokenBusyboxException - */ - public String getSymlink(String file) throws BrokenBusyboxException, TimeoutException, - IOException { - Log.d(RootCommands.TAG, "Find symlink for " + file); - - String symlink = null; - - LsCommand lsCommand = new LsCommand(file); - shell.add(lsCommand).waitForFinish(); - - symlink = lsCommand.getSymlink(); - - return symlink; - } - - /** - * Copys a file to a destination. Because cp is not available on all android devices, we use dd - * or cat. - * - * @param source - * example: /data/data/org.adaway/files/hosts - * @param destination - * example: /system/etc/hosts - * @param remountAsRw - * remounts the destination as read/write before writing to it - * @param preserveFileAttributes - * tries to copy file attributes from source to destination, if only cat is available - * only permissions are preserved - * @return true if it was successfully copied - * @throws BrokenBusyboxException - * @throws IOException - * @throws TimeoutException - */ - public boolean copyFile(String source, String destination, boolean remountAsRw, - boolean preservePermissions) throws BrokenBusyboxException, IOException, - TimeoutException { - - /* - * dd can only copy files, but we can not check if the source is a file without invoking - * shell commands, because from Java we probably have no read access, thus we only check if - * they are ending with trailing slashes - */ - if (source.endsWith("/") || destination.endsWith("/")) { - throw new FileNotFoundException("dd can only copy files!"); - } - - // remount destination as read/write before copying to it - if (remountAsRw) { - if (!remount(destination, "RW")) { - Log.d(RootCommands.TAG, - "Remounting failed! There is probably no need to remount this partition!"); - } - } - - // get permissions of source before overwriting - String permissions = null; - if (preservePermissions) { - permissions = getFilePermissions(source); - } - - boolean commandSuccess = false; - - SimpleCommand ddCommand = new SimpleCommand("dd if=" + source + " of=" - + destination); - shell.add(ddCommand).waitForFinish(); - - if (ddCommand.getExitCode() == 0) { - commandSuccess = true; - } else { - // try cat if dd fails - SimpleCommand catCommand = new SimpleCommand("cat " + source + " > " - + destination); - shell.add(catCommand).waitForFinish(); - - if (catCommand.getExitCode() == 0) { - commandSuccess = true; - } - } - - // set back permissions from source to destination - if (preservePermissions) { - setFilePermissions(destination, permissions); - } - - // remount destination back to read only - if (remountAsRw) { - if (!remount(destination, "RO")) { - Log.d(RootCommands.TAG, - "Remounting failed! There is probably no need to remount this partition!"); - } - } - - return commandSuccess; - } - - public static final int REBOOT_HOTREBOOT = 1; - public static final int REBOOT_REBOOT = 2; - public static final int REBOOT_SHUTDOWN = 3; - public static final int REBOOT_RECOVERY = 4; - - /** - * Shutdown or reboot device. Possible actions are REBOOT_HOTREBOOT, REBOOT_REBOOT, - * REBOOT_SHUTDOWN, REBOOT_RECOVERY - * - * @param action - * @throws IOException - * @throws TimeoutException - * @throws BrokenBusyboxException - */ - public void reboot(int action) throws BrokenBusyboxException, TimeoutException, IOException { - if (action == REBOOT_HOTREBOOT) { - killAll("system_server"); - // or: killAll("zygote"); - } else { - String command; - switch (action) { - case REBOOT_REBOOT: - command = "reboot"; - break; - case REBOOT_SHUTDOWN: - command = "reboot -p"; - break; - case REBOOT_RECOVERY: - command = "reboot recovery"; - break; - default: - command = "reboot"; - break; - } - - SimpleCommand rebootCommand = new SimpleCommand(command); - shell.add(rebootCommand).waitForFinish(); - - if (rebootCommand.getExitCode() == -1) { - Log.e(RootCommands.TAG, "Reboot failed!"); - } - } - } - - /** - * This command checks if a file exists - */ - private class FileExistsCommand extends Command { - private String file; - private boolean fileExists = false; - - public FileExistsCommand(String file) { - super("ls " + file); - this.file = file; - } - - public boolean isFileExists() { - return fileExists; - } - - @Override - public void output(int id, String line) { - if (line.trim().equals(file)) { - fileExists = true; - } - } - - @Override - public void afterExecution(int id, int exitCode) { - } - - } - - /** - * Use this to check whether or not a file exists on the filesystem. - * - * @param file - * String that represent the file, including the full path to the file and its name. - * - * @return a boolean that will indicate whether or not the file exists. - * @throws IOException - * @throws TimeoutException - * @throws BrokenBusyboxException - * - */ - public boolean fileExists(String file) throws BrokenBusyboxException, TimeoutException, - IOException { - FileExistsCommand fileExistsCommand = new FileExistsCommand(file); - shell.add(fileExistsCommand).waitForFinish(); - - if (fileExistsCommand.isFileExists()) { - return true; - } else { - return false; - } - } - - public abstract class WithPermissions { - abstract void whileHavingPermissions(); - } - - /** - * Execute user defined Java code while having temporary permissions on a file - * - * @param file - * @param withPermissions - * @throws BrokenBusyboxException - * @throws TimeoutException - * @throws IOException - */ - public void withPermission(String file, String permission, WithPermissions withPermissions) - throws BrokenBusyboxException, TimeoutException, IOException { - String oldPermissions = getFilePermissions(file); - - // set permissions (If set to 666, then Dalvik VM can also write to that file!) - setFilePermissions(file, permission); - - // execute user defined code - withPermissions.whileHavingPermissions(); - - // set back to old permissions - setFilePermissions(file, oldPermissions); - } - - /** - * Execute user defined Java code while having temporary write permissions on a file using chmod - * 666 - * - * @param file - * @param withWritePermissions - * @throws BrokenBusyboxException - * @throws TimeoutException - * @throws IOException - */ - public void withWritePermissions(String file, WithPermissions withWritePermissions) - throws BrokenBusyboxException, TimeoutException, IOException { - withPermission(file, "666", withWritePermissions); - } - - /** - * Sets system clock using /dev/alarm - * - * @param millis - * @throws BrokenBusyboxException - * @throws TimeoutException - * @throws IOException - */ - public void setSystemClock(final long millis) throws BrokenBusyboxException, TimeoutException, - IOException { - withWritePermissions("/dev/alarm", new WithPermissions() { - - @Override - void whileHavingPermissions() { - SystemClock.setCurrentTimeMillis(millis); - } - }); - } - - /** - * Adjust system clock by offset using /dev/alarm - * - * @param offset - * @throws BrokenBusyboxException - * @throws TimeoutException - * @throws IOException - */ - public void adjustSystemClock(final long offset) throws BrokenBusyboxException, - TimeoutException, IOException { - withWritePermissions("/dev/alarm", new WithPermissions() { - - @Override - void whileHavingPermissions() { - SystemClock.setCurrentTimeMillis(System.currentTimeMillis() + offset); - } - }); - } - - /** - * This will take a path, which can contain the file name as well, and attempt to remount the - * underlying partition. - * - * For example, passing in the following string: - * "/system/bin/some/directory/that/really/would/never/exist" will result in /system ultimately - * being remounted. However, keep in mind that the longer the path you supply, the more work - * this has to do, and the slower it will run. - * - * @param file - * file path - * @param mountType - * mount type: pass in RO (Read only) or RW (Read Write) - * @return a boolean which indicates whether or not the partition has been - * remounted as specified. - */ - public boolean remount(String file, String mountType) { - // Recieved a request, get an instance of Remounter - Remounter remounter = new Remounter(shell); - // send the request - return (remounter.remount(file, mountType)); - } - - /** - * This will tell you how the specified mount is mounted. rw, ro, etc... - * - * @param The - * mount you want to check - * - * @return String What the mount is mounted as. - * @throws Exception - * if we cannot determine how the mount is mounted. - */ - public String getMountedAs(String path) throws Exception { - ArrayList mounts = Remounter.getMounts(); - if (mounts != null) { - for (Mount mount : mounts) { - if (path.contains(mount.getMountPoint().getAbsolutePath())) { - Log.d(RootCommands.TAG, (String) mount.getFlags().toArray()[0]); - return (String) mount.getFlags().toArray()[0]; - } - } - - throw new Exception(); - } else { - throw new Exception(); - } - } - - /** - * Check if there is enough space on partition where target is located - * - * @param size - * size of file to put on partition - * @param target - * path where to put the file - * - * @return true if it will fit on partition of target, false if it will not fit. - */ - public boolean hasEnoughSpaceOnPartition(String target, long size) { - try { - // new File(target).getFreeSpace() (API 9) is not working on data partition - - // get directory without file - String directory = new File(target).getParent().toString(); - - StatFs stat = new StatFs(directory); - long blockSize = stat.getBlockSize(); - long availableBlocks = stat.getAvailableBlocks(); - long availableSpace = availableBlocks * blockSize; - - Log.i(RootCommands.TAG, "Checking for enough space: Target: " + target - + ", directory: " + directory + " size: " + size + ", availableSpace: " - + availableSpace); - - if (size < availableSpace) { - return true; - } else { - Log.e(RootCommands.TAG, "Not enough space on partition!"); - return false; - } - } catch (Exception e) { - // if new StatFs(directory) fails catch IllegalArgumentException and just return true as - // workaround - Log.e(RootCommands.TAG, "Problem while getting available space on partition!", e); - return true; - } - } - - /** - * TODO: Not tested! - * - * @param toggle - * @throws IOException - * @throws TimeoutException - * @throws BrokenBusyboxException - */ - public void toggleAdbDaemon(boolean toggle) throws BrokenBusyboxException, TimeoutException, - IOException { - SimpleCommand disableAdb = new SimpleCommand("setprop persist.service.adb.enable 0", - "stop adbd"); - SimpleCommand enableAdb = new SimpleCommand("setprop persist.service.adb.enable 1", - "stop adbd", "sleep 1", "start adbd"); - - if (toggle) { - shell.add(enableAdb).waitForFinish(); - } else { - shell.add(disableAdb).waitForFinish(); - } - } - -} diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/Command.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/Command.java deleted file mode 100644 index 33e0f19..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/Command.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks, Jeremy Lakeman (RootTools) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands.command; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.concurrent.TimeoutException; - -import org.sufficientlysecure.rootcommands.RootCommands; -import org.sufficientlysecure.rootcommands.Shell; -import org.sufficientlysecure.rootcommands.util.BrokenBusyboxException; -import org.sufficientlysecure.rootcommands.util.Log; - -public abstract class Command { - final String command[]; - boolean finished = false; - boolean brokenBusyboxDetected = false; - int exitCode; - int id; - int timeout = RootCommands.DEFAULT_TIMEOUT; - Shell shell = null; - - public Command(String... command) { - this.command = command; - } - - public Command(int timeout, String... command) { - this.command = command; - this.timeout = timeout; - } - - /** - * This is called from Shell after adding it - * - * @param shell - * @param id - */ - public void addedToShell(Shell shell, int id) { - this.shell = shell; - this.id = id; - } - - /** - * Gets command string executed on the shell - * - * @return - */ - public String getCommand() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < command.length; i++) { - // redirect stderr to stdout - sb.append(command[i] + " 2>&1"); - sb.append('\n'); - } - Log.d(RootCommands.TAG, "Sending command(s): " + sb.toString()); - return sb.toString(); - } - - public void writeCommand(OutputStream out) throws IOException { - out.write(getCommand().getBytes()); - } - - public void processOutput(String line) { - Log.d(RootCommands.TAG, "ID: " + id + ", Output: " + line); - - /* - * Try to detect broken toolbox/busybox binaries (see - * https://code.google.com/p/busybox-android/issues/detail?id=1) - * - * It is giving "Value too large for defined data type" on certain file operations (e.g. ls - * and chown) in certain directories (e.g. /data/data) - */ - if (line.contains("Value too large for defined data type")) { - Log.e(RootCommands.TAG, "Busybox is broken with high probability due to line: " + line); - brokenBusyboxDetected = true; - } - - // now execute specific output parsing - output(id, line); - } - - public abstract void output(int id, String line); - - public void processAfterExecution(int exitCode) { - Log.d(RootCommands.TAG, "ID: " + id + ", ExitCode: " + exitCode); - - afterExecution(id, exitCode); - } - - public abstract void afterExecution(int id, int exitCode); - - public void commandFinished(int id) { - Log.d(RootCommands.TAG, "Command " + id + " finished."); - } - - public void setExitCode(int code) { - synchronized (this) { - exitCode = code; - finished = true; - commandFinished(id); - this.notifyAll(); - } - } - - /** - * Close the shell - * - * @param reason - */ - public void terminate(String reason) { - try { - shell.close(); - Log.d(RootCommands.TAG, "Terminating the shell."); - terminated(reason); - } catch (IOException e) { - } - } - - public void terminated(String reason) { - setExitCode(-1); - Log.d(RootCommands.TAG, "Command " + id + " did not finish, because of " + reason); - } - - /** - * Waits for this command to finish and forwards exitCode into afterExecution method - * - * @param timeout - * @throws TimeoutException - * @throws BrokenBusyboxException - */ - public void waitForFinish() throws TimeoutException, BrokenBusyboxException { - synchronized (this) { - while (!finished) { - try { - this.wait(timeout); - } catch (InterruptedException e) { - Log.e(RootCommands.TAG, "InterruptedException in waitForFinish()", e); - } - - if (!finished) { - finished = true; - terminate("Timeout"); - throw new TimeoutException("Timeout has occurred."); - } - } - - if (brokenBusyboxDetected) { - throw new BrokenBusyboxException(); - } - - processAfterExecution(exitCode); - } - } - -} \ No newline at end of file diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/ExecutableCommand.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/ExecutableCommand.java deleted file mode 100644 index d6c8e61..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/ExecutableCommand.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands.command; - -import java.io.File; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Build; - -public abstract class ExecutableCommand extends Command { - public static final String EXECUTABLE_PREFIX = "lib"; - public static final String EXECUTABLE_SUFFIX = "_exec.so"; - - /** - * This class provides a way to use your own binaries! - * - * Include your own executables, renamed from * to lib*_exec.so, in your libs folder under the - * architecture directories. Now they will be deployed by Android the same way libraries are - * deployed! - * - * See README for more information how to use your own executables! - * - * @param context - * @param executableName - * @param parameters - */ - public ExecutableCommand(Context context, String executableName, String parameters) { - super(getLibDirectory(context) + File.separator + EXECUTABLE_PREFIX + executableName - + EXECUTABLE_SUFFIX + " " + parameters); - } - - /** - * Get full path to lib directory of app - * - * @return dir as String - */ - @SuppressLint("NewApi") - private static String getLibDirectory(Context context) { - if (Build.VERSION.SDK_INT >= 9) { - return context.getApplicationInfo().nativeLibraryDir; - } else { - return context.getApplicationInfo().dataDir + File.separator + "lib"; - } - } - - public abstract void output(int id, String line); - - public abstract void afterExecution(int id, int exitCode); - -} \ No newline at end of file diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleCommand.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleCommand.java deleted file mode 100644 index 9049040..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands.command; - -public class SimpleCommand extends Command { - private StringBuilder sb = new StringBuilder(); - - public SimpleCommand(String... command) { - super(command); - } - - @Override - public void output(int id, String line) { - sb.append(line).append('\n'); - } - - @Override - public void afterExecution(int id, int exitCode) { - } - - public String getOutput() { - return sb.toString(); - } - - public int getExitCode() { - return exitCode; - } - -} \ No newline at end of file diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleExecutableCommand.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleExecutableCommand.java deleted file mode 100644 index 95d2fae..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleExecutableCommand.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands.command; - -import android.content.Context; - -public class SimpleExecutableCommand extends ExecutableCommand { - private StringBuilder sb = new StringBuilder(); - - public SimpleExecutableCommand(Context context, String executableName, String parameters) { - super(context, executableName, parameters); - } - - @Override - public void output(int id, String line) { - sb.append(line).append('\n'); - } - - @Override - public void afterExecution(int id, int exitCode) { - } - - public String getOutput() { - return sb.toString(); - } - - public int getExitCode() { - return exitCode; - } - -} \ No newline at end of file diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/BrokenBusyboxException.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/BrokenBusyboxException.java deleted file mode 100644 index e982b24..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/BrokenBusyboxException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands.util; - -import java.io.IOException; - -public class BrokenBusyboxException extends IOException { - private static final long serialVersionUID = 8337358201589488409L; - - public BrokenBusyboxException() { - super(); - } - - public BrokenBusyboxException(String detailMessage) { - super(detailMessage); - } - -} diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/Log.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/Log.java deleted file mode 100644 index a25fbf4..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/Log.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands.util; - -import org.sufficientlysecure.rootcommands.RootCommands; - -/** - * Wraps Android Logging to enable or disable debug output using Constants - * - */ -public final class Log { - - public static void v(String tag, String msg) { - if (RootCommands.DEBUG) { - android.util.Log.v(tag, msg); - } - } - - public static void v(String tag, String msg, Throwable tr) { - if (RootCommands.DEBUG) { - android.util.Log.v(tag, msg, tr); - } - } - - public static void d(String tag, String msg) { - if (RootCommands.DEBUG) { - android.util.Log.d(tag, msg); - } - } - - public static void d(String tag, String msg, Throwable tr) { - if (RootCommands.DEBUG) { - android.util.Log.d(tag, msg, tr); - } - } - - public static void i(String tag, String msg) { - if (RootCommands.DEBUG) { - android.util.Log.i(tag, msg); - } - } - - public static void i(String tag, String msg, Throwable tr) { - if (RootCommands.DEBUG) { - android.util.Log.i(tag, msg, tr); - } - } - - public static void w(String tag, String msg) { - android.util.Log.w(tag, msg); - } - - public static void w(String tag, String msg, Throwable tr) { - android.util.Log.w(tag, msg, tr); - } - - public static void w(String tag, Throwable tr) { - android.util.Log.w(tag, tr); - } - - public static void e(String tag, String msg) { - android.util.Log.e(tag, msg); - } - - public static void e(String tag, String msg, Throwable tr) { - android.util.Log.e(tag, msg, tr); - } - -} diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/RootAccessDeniedException.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/RootAccessDeniedException.java deleted file mode 100644 index 35f353d..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/RootAccessDeniedException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands.util; - -import java.io.IOException; - -public class RootAccessDeniedException extends IOException { - private static final long serialVersionUID = 9088998884166225540L; - - public RootAccessDeniedException() { - super(); - } - - public RootAccessDeniedException(String detailMessage) { - super(detailMessage); - } - -} diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/UnsupportedArchitectureException.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/UnsupportedArchitectureException.java deleted file mode 100644 index 96ad030..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/UnsupportedArchitectureException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands.util; - -public class UnsupportedArchitectureException extends Exception { - private static final long serialVersionUID = 7826528799780001655L; - - public UnsupportedArchitectureException() { - super(); - } - - public UnsupportedArchitectureException(String detailMessage) { - super(detailMessage); - } - -} diff --git a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/Utils.java b/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/Utils.java deleted file mode 100644 index 87f32bb..0000000 --- a/libraries/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/Utils.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (c) 2012 Michael Elsdörfer (Android Autostarts) - * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks (RootTools) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.rootcommands.util; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Map; - -import org.sufficientlysecure.rootcommands.RootCommands; - -public class Utils { - /* - * The emulator and ADP1 device both have a su binary in /system/xbin/su, but it doesn't allow - * apps to use it (su app_29 $ su su: uid 10029 not allowed to su). - * - * Cyanogen used to have su in /system/bin/su, in newer versions it's a symlink to - * /system/xbin/su. - * - * The Archos tablet has it in /data/bin/su, since they don't have write access to /system yet. - */ - static final String[] BinaryPlaces = { "/data/bin/", "/system/bin/", "/system/xbin/", "/sbin/", - "/data/local/xbin/", "/data/local/bin/", "/system/sd/xbin/", "/system/bin/failsafe/", - "/data/local/" }; - - /** - * Determine the path of the su executable. - * - * Code from https://github.com/miracle2k/android-autostarts, use under Apache License was - * agreed by Michael Elsdörfer - */ - public static String getSuPath() { - for (String p : BinaryPlaces) { - File su = new File(p + "su"); - if (su.exists()) { - Log.d(RootCommands.TAG, "su found at: " + p); - return su.getAbsolutePath(); - } else { - Log.v(RootCommands.TAG, "No su in: " + p); - } - } - Log.d(RootCommands.TAG, "No su found in a well-known location, " + "will just use \"su\"."); - return "su"; - } - - /** - * This code is adapted from java.lang.ProcessBuilder.start(). - * - * The problem is that Android doesn't allow us to modify the map returned by - * ProcessBuilder.environment(), even though the docstring indicates that it should. This is - * because it simply returns the SystemEnvironment object that System.getenv() gives us. The - * relevant portion in the source code is marked as "// android changed", so presumably it's not - * the case in the original version of the Apache Harmony project. - * - * Note that simply passing the environment variables we want to Process.exec won't be good - * enough, since that would override the environment we inherited completely. - * - * We needed to be able to set a CLASSPATH environment variable for our new process in order to - * use the "app_process" command directly. Note: "app_process" takes arguments passed on to the - * Dalvik VM as well; this might be an alternative way to set the class path. - * - * Code from https://github.com/miracle2k/android-autostarts, use under Apache License was - * agreed by Michael Elsdörfer - */ - public static Process runWithEnv(String command, ArrayList customAddedEnv, - String baseDirectory) throws IOException { - - Map environment = System.getenv(); - String[] envArray = new String[environment.size() - + (customAddedEnv != null ? customAddedEnv.size() : 0)]; - int i = 0; - for (Map.Entry entry : environment.entrySet()) { - envArray[i++] = entry.getKey() + "=" + entry.getValue(); - } - if (customAddedEnv != null) { - for (String entry : customAddedEnv) { - envArray[i++] = entry; - } - } - - Process process; - if (baseDirectory == null) { - process = Runtime.getRuntime().exec(command, envArray, null); - } else { - process = Runtime.getRuntime().exec(command, envArray, new File(baseDirectory)); - } - return process; - } -} diff --git a/libraries/libsuperuser b/libraries/libsuperuser new file mode 160000 index 0000000..abf0553 --- /dev/null +++ b/libraries/libsuperuser @@ -0,0 +1 @@ +Subproject commit abf0553fc1b4d45c9fc646453fa5cce4636f310a diff --git a/settings.gradle b/settings.gradle index b841800..72f34c6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ -include ':app', ':libraries:RootCommands' +include ':app', ':libsuperuser' +project(':libsuperuser').projectDir = new File('libraries/libsuperuser/libsuperuser') \ No newline at end of file diff --git a/zertman.iml b/zertman.iml index edb62a6..0bb6048 100644 --- a/zertman.iml +++ b/zertman.iml @@ -1,5 +1,12 @@ + + + + + +