Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
M
Magisk
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Magisk
Commits
4a48f59d
Commit
4a48f59d
authored
Aug 20, 2016
by
dvdandroid
Committed by
topjohnwu
Aug 22, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Changed root method
parent
bc2c63bf
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
2287 additions
and
201 deletions
+2287
-201
gradle.xml
.idea/gradle.xml
+4
-0
misc.xml
.idea/misc.xml
+0
-41
modules.xml
.idea/modules.xml
+2
-0
build.gradle
app/build.gradle
+1
-0
ApplicationTest.java
...ndroidTest/java/com/topjohnwu/magisk/ApplicationTest.java
+0
-13
AndroidManifest.xml
app/src/main/AndroidManifest.xml
+2
-2
Module.java
app/src/main/java/com/topjohnwu/magisk/model/Module.java
+1
-1
ModulesAdapter.java
...src/main/java/com/topjohnwu/magisk/rv/ModulesAdapter.java
+60
-0
MainActivity.java
app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.java
+16
-73
ModulesActivity.java
...rc/main/java/com/topjohnwu/magisk/ui/ModulesActivity.java
+5
-53
Utils.java
app/src/main/java/com/topjohnwu/magisk/ui/utils/Utils.java
+28
-0
activity_main.xml
app/src/main/res/layout/activity_main.xml
+1
-1
activity_modules.xml
app/src/main/res/layout/activity_modules.xml
+1
-1
ExampleUnitTest.java
app/src/test/java/com/topjohnwu/magisk/ExampleUnitTest.java
+0
-15
.gitignore
lib/RootCommands/.gitignore
+2
-0
build.gradle
lib/RootCommands/build.gradle
+20
-0
AndroidManifest.xml
lib/RootCommands/src/main/AndroidManifest.xml
+6
-0
Mount.java
.../main/java/org/sufficientlysecure/rootcommands/Mount.java
+58
-0
Remounter.java
...n/java/org/sufficientlysecure/rootcommands/Remounter.java
+188
-0
RootCommands.java
...ava/org/sufficientlysecure/rootcommands/RootCommands.java
+49
-0
Shell.java
.../main/java/org/sufficientlysecure/rootcommands/Shell.java
+347
-0
SystemCommands.java
...a/org/sufficientlysecure/rootcommands/SystemCommands.java
+120
-0
Toolbox.java
...ain/java/org/sufficientlysecure/rootcommands/Toolbox.java
+772
-0
Command.java
.../org/sufficientlysecure/rootcommands/command/Command.java
+169
-0
ExecutableCommand.java
...cientlysecure/rootcommands/command/ExecutableCommand.java
+65
-0
SimpleCommand.java
...ufficientlysecure/rootcommands/command/SimpleCommand.java
+43
-0
SimpleExecutableCommand.java
...ysecure/rootcommands/command/SimpleExecutableCommand.java
+45
-0
BrokenBusyboxException.java
...entlysecure/rootcommands/util/BrokenBusyboxException.java
+32
-0
Log.java
...in/java/org/sufficientlysecure/rootcommands/util/Log.java
+82
-0
RootAccessDeniedException.java
...lysecure/rootcommands/util/RootAccessDeniedException.java
+32
-0
UnsupportedArchitectureException.java
...e/rootcommands/util/UnsupportedArchitectureException.java
+30
-0
Utils.java
.../java/org/sufficientlysecure/rootcommands/util/Utils.java
+105
-0
settings.gradle
settings.gradle
+1
-1
No files found.
.idea/gradle.xml
View file @
4a48f59d
...
...
@@ -10,12 +10,16 @@
<set>
<option
value=
"$PROJECT_DIR$"
/>
<option
value=
"$PROJECT_DIR$/app"
/>
<option
value=
"$PROJECT_DIR$/lib"
/>
<option
value=
"$PROJECT_DIR$/lib/RootCommands"
/>
</set>
</option>
<option
name=
"myModules"
>
<set>
<option
value=
"$PROJECT_DIR$"
/>
<option
value=
"$PROJECT_DIR$/app"
/>
<option
value=
"$PROJECT_DIR$/lib"
/>
<option
value=
"$PROJECT_DIR$/lib/RootCommands"
/>
</set>
</option>
</GradleProjectSettings>
...
...
.idea/misc.xml
View file @
4a48f59d
...
...
@@ -27,47 +27,6 @@
</value>
</option>
</component>
<component
name=
"ProjectInspectionProfilesVisibleTreeState"
>
<entry
key=
"Project Default"
>
<profile-state>
<expanded-state>
<State>
<id>
Android Lint
</id>
</State>
<State>
<id>
Groovy
</id>
</State>
<State>
<id>
HTML
</id>
</State>
<State>
<id>
J2ME issues
</id>
</State>
<State>
<id>
JUnit issues
</id>
</State>
<State>
<id>
Java language level issues
</id>
</State>
<State>
<id>
JavaBeans issues
</id>
</State>
<State>
<id>
Pattern Validation
</id>
</State>
<State>
<id>
Threading issues
</id>
</State>
<State>
<id>
Threading issuesGroovy
</id>
</State>
<State>
<id>
XML
</id>
</State>
</expanded-state>
</profile-state>
</entry>
</component>
<component
name=
"ProjectLevelVcsManager"
settingsEditedManually=
"false"
>
<OptionsSetting
value=
"true"
id=
"Add"
/>
<OptionsSetting
value=
"true"
id=
"Remove"
/>
...
...
.idea/modules.xml
View file @
4a48f59d
...
...
@@ -3,7 +3,9 @@
<component
name=
"ProjectModuleManager"
>
<modules>
<module
fileurl=
"file://$PROJECT_DIR$/MagiskManager.iml"
filepath=
"$PROJECT_DIR$/MagiskManager.iml"
/>
<module
fileurl=
"file://$PROJECT_DIR$/lib/RootCommands/RootCommands.iml"
filepath=
"$PROJECT_DIR$/lib/RootCommands/RootCommands.iml"
/>
<module
fileurl=
"file://$PROJECT_DIR$/app/app.iml"
filepath=
"$PROJECT_DIR$/app/app.iml"
/>
<module
fileurl=
"file://$PROJECT_DIR$/lib/lib.iml"
filepath=
"$PROJECT_DIR$/lib/lib.iml"
/>
</modules>
</component>
</project>
\ No newline at end of file
app/build.gradle
View file @
4a48f59d
...
...
@@ -25,4 +25,5 @@ android {
dependencies
{
compile
fileTree
(
dir:
'libs'
,
include:
[
'*.jar'
])
compile
project
(
':lib:RootCommands'
)
}
app/src/androidTest/java/com/topjohnwu/magisk/ApplicationTest.java
deleted
100644 → 0
View file @
bc2c63bf
package
com
.
topjohnwu
.
magisk
;
import
android.app.Application
;
import
android.test.ApplicationTestCase
;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public
class
ApplicationTest
extends
ApplicationTestCase
<
Application
>
{
public
ApplicationTest
()
{
super
(
Application
.
class
);
}
}
\ No newline at end of file
app/src/main/AndroidManifest.xml
View file @
4a48f59d
...
...
@@ -8,14 +8,14 @@
android:label=
"@string/app_name"
android:supportsRtl=
"true"
android:theme=
"@style/AppTheme"
>
<activity
android:name=
".MainActivity"
>
<activity
android:name=
".
ui.
MainActivity"
>
<intent-filter>
<action
android:name=
"android.intent.action.MAIN"
/>
<category
android:name=
"android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity
android:name=
".ModulesActivity"
>
<activity
android:name=
".
ui.
ModulesActivity"
>
</activity>
</application>
...
...
app/src/main/java/com/topjohnwu/magisk/Module.java
→
app/src/main/java/com/topjohnwu/magisk/
model/
Module.java
View file @
4a48f59d
package
com
.
topjohnwu
.
magisk
;
package
com
.
topjohnwu
.
magisk
.
model
;
import
java.io.BufferedReader
;
import
java.io.File
;
...
...
app/src/main/java/com/topjohnwu/magisk/rv/ModulesAdapter.java
0 → 100644
View file @
4a48f59d
package
com
.
topjohnwu
.
magisk
.
rv
;
import
android.annotation.SuppressLint
;
import
android.content.Context
;
import
android.view.LayoutInflater
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.widget.ArrayAdapter
;
import
android.widget.TextView
;
import
com.topjohnwu.magisk.R
;
import
com.topjohnwu.magisk.model.Module
;
import
java.util.List
;
public
class
ModulesAdapter
extends
ArrayAdapter
<
Module
>
{
public
ModulesAdapter
(
Context
context
,
int
resource
,
List
<
Module
>
modules
)
{
super
(
context
,
resource
,
modules
);
}
@SuppressLint
(
"SetTextI18n"
)
@Override
public
View
getView
(
int
position
,
View
convertView
,
ViewGroup
parent
)
{
ViewHolder
vh
;
if
(
convertView
==
null
)
{
LayoutInflater
inflater
=
(
LayoutInflater
)
getContext
().
getSystemService
(
Context
.
LAYOUT_INFLATER_SERVICE
);
convertView
=
inflater
.
inflate
(
R
.
layout
.
row
,
null
);
vh
=
new
ViewHolder
();
vh
.
name
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
name
);
vh
.
version
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
version
);
vh
.
versionCode
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
versionCode
);
vh
.
description
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
description
);
vh
.
cache
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
cache
);
convertView
.
setTag
(
vh
);
}
else
{
vh
=
(
ViewHolder
)
convertView
.
getTag
();
}
Module
module
=
getItem
(
position
);
vh
.
name
.
setText
(
"name= "
+
module
.
getName
());
vh
.
version
.
setText
(
"version= "
+
module
.
getVersion
());
vh
.
versionCode
.
setText
(
"versioncode= "
+
module
.
getVersionCode
());
vh
.
description
.
setText
(
"description= "
+
module
.
getDescription
());
vh
.
cache
.
setText
(
"is from cache= "
+
module
.
isCache
());
return
convertView
;
}
static
class
ViewHolder
{
public
TextView
name
;
public
TextView
version
;
public
TextView
versionCode
;
public
TextView
description
;
public
TextView
cache
;
}
}
\ No newline at end of file
app/src/main/java/com/topjohnwu/magisk/MainActivity.java
→
app/src/main/java/com/topjohnwu/magisk/
ui/
MainActivity.java
View file @
4a48f59d
package
com
.
topjohnwu
.
magisk
;
package
com
.
topjohnwu
.
magisk
.
ui
;
import
android.app.Activity
;
import
android.content.Intent
;
import
android.graphics.Color
;
import
android.os.AsyncTask
;
import
android.os.Bundle
;
import
android.os.Handler
;
import
android.widget.Switch
;
import
android.widget.TextView
;
import
java.io.BufferedReader
;
import
java.io.DataOutputStream
;
import
com.topjohnwu.magisk.R
;
import
java.io.File
;
import
java.io.IOException
;
import
java.io.InputStreamReader
;
import
static
com
.
topjohnwu
.
magisk
.
ui
.
utils
.
Utils
.
executeCommand
;
public
class
MainActivity
extends
Activity
{
...
...
@@ -34,59 +32,29 @@ public class MainActivity extends Activity {
safetyNet
=
(
TextView
)
findViewById
(
R
.
id
.
safety_net
);
permissive
=
(
TextView
)
findViewById
(
R
.
id
.
permissive
);
suPath
=
execute
(
"getprop magisk.supath"
);
suPath
=
execute
Command
(
"getprop magisk.supath"
);
updateStatus
();
rootToggle
.
setOnClickListener
(
view
->
{
Switch
s
=
(
Switch
)
view
;
if
(
s
.
isChecked
())
{
(
new
SU
()).
execute
(
"setprop magisk.root 1"
);
}
else
{
(
new
SU
()).
execute
(
"setprop magisk.root 0"
);
}
rootToggle
.
setOnCheckedChangeListener
((
view
,
checked
)
->
{
executeCommand
(
checked
?
"setprop magisk.root 1"
:
"setprop magisk.root 0"
);
updateStatus
();
});
selinuxToggle
.
setOnClickListener
(
view
->
{
Switch
s
=
(
Switch
)
view
;
if
(
s
.
isChecked
())
{
new
SU
().
execute
(
"setenforce 1"
);
}
else
{
new
SU
().
execute
(
"setenforce 0"
);
}
selinuxToggle
.
setOnCheckedChangeListener
((
view
,
checked
)
->
{
executeCommand
(
checked
?
"setenforce 1"
:
"setenforce 0"
);
updateStatus
();
});
findViewById
(
R
.
id
.
modules
).
setOnClickListener
(
view
->
startActivity
(
new
Intent
(
this
,
ModulesActivity
.
class
)));
}
private
String
execute
(
String
command
)
{
StringBuilder
output
=
new
StringBuilder
();
Process
p
;
try
{
p
=
Runtime
.
getRuntime
().
exec
(
command
);
p
.
waitFor
();
BufferedReader
reader
=
new
BufferedReader
(
new
InputStreamReader
(
p
.
getInputStream
()));
String
line
;
while
((
line
=
reader
.
readLine
())
!=
null
)
{
if
(
output
.
length
()
!=
0
)
output
.
append
(
"\n"
);
output
.
append
(
line
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
return
output
.
toString
();
}
private
void
updateStatus
()
{
String
selinux
=
executeCommand
(
"getenforce"
);
String
selinux
=
execute
(
"getenforce"
);
magiskVersion
.
setText
(
getString
(
R
.
string
.
magisk_version
,
execute
(
"getprop magisk.version"
)));
magiskVersion
.
setText
(
getString
(
R
.
string
.
magisk_version
,
executeCommand
(
"getprop magisk.version"
)));
selinuxStatus
.
setText
(
selinux
);
assert
selinux
!=
null
;
if
(
selinux
.
equals
(
"Enforcing"
))
{
selinuxStatus
.
setTextColor
(
Color
.
GREEN
);
selinuxToggle
.
setChecked
(
true
);
...
...
@@ -109,7 +77,7 @@ public class MainActivity extends Activity {
safetyNet
.
setTextColor
(
Color
.
RED
);
rootToggle
.
setChecked
(
true
);
if
(!
(
new
File
(
suPath
+
"/su"
)
).
exists
())
{
if
(!
new
File
(
suPath
+
"/su"
).
exists
())
{
rootStatus
.
setText
(
R
.
string
.
root_system
);
safetyNet
.
setText
(
R
.
string
.
root_system_info
);
rootToggle
.
setEnabled
(
false
);
...
...
@@ -135,29 +103,4 @@ public class MainActivity extends Activity {
}
}
protected
class
SU
extends
AsyncTask
<
String
,
Void
,
Void
>
{
@Override
protected
Void
doInBackground
(
String
...
params
)
{
try
{
Process
su
=
Runtime
.
getRuntime
().
exec
(
suPath
+
"/su"
);
DataOutputStream
out
=
new
DataOutputStream
(
su
.
getOutputStream
());
for
(
String
command
:
params
)
{
out
.
writeBytes
(
command
+
"\n"
);
out
.
flush
();
}
out
.
writeBytes
(
"exit\n"
);
out
.
flush
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
return
null
;
}
@Override
protected
void
onPostExecute
(
Void
aVoid
)
{
final
Handler
handler
=
new
Handler
();
handler
.
postDelayed
(
MainActivity
.
this
::
updateStatus
,
1500
);
}
}
}
app/src/main/java/com/topjohnwu/magisk/ModulesActivity.java
→
app/src/main/java/com/topjohnwu/magisk/
ui/
ModulesActivity.java
View file @
4a48f59d
package
com
.
topjohnwu
.
magisk
;
package
com
.
topjohnwu
.
magisk
.
ui
;
import
android.annotation.SuppressLint
;
import
android.app.Activity
;
import
android.app.ProgressDialog
;
import
android.content.Context
;
import
android.os.AsyncTask
;
import
android.os.Bundle
;
import
android.view.LayoutInflater
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.widget.ArrayAdapter
;
import
android.widget.ListView
;
import
android.widget.TextView
;
import
com.topjohnwu.magisk.R
;
import
com.topjohnwu.magisk.model.Module
;
import
com.topjohnwu.magisk.rv.ModulesAdapter
;
import
java.io.File
;
import
java.util.ArrayList
;
...
...
@@ -90,49 +87,4 @@ public class ModulesActivity extends Activity {
}
}
private
class
ModulesAdapter
extends
ArrayAdapter
<
Module
>
{
public
ModulesAdapter
(
Context
context
,
int
resource
,
List
<
Module
>
modules
)
{
super
(
context
,
resource
,
modules
);
}
@SuppressLint
(
"SetTextI18n"
)
@Override
public
View
getView
(
int
position
,
View
convertView
,
ViewGroup
parent
)
{
ViewHolder
vh
;
if
(
convertView
==
null
)
{
LayoutInflater
inflater
=
(
LayoutInflater
)
getContext
().
getSystemService
(
Context
.
LAYOUT_INFLATER_SERVICE
);
convertView
=
inflater
.
inflate
(
R
.
layout
.
row
,
null
);
vh
=
new
ViewHolder
();
vh
.
name
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
name
);
vh
.
version
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
version
);
vh
.
versionCode
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
versionCode
);
vh
.
description
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
description
);
vh
.
cache
=
(
TextView
)
convertView
.
findViewById
(
R
.
id
.
cache
);
convertView
.
setTag
(
vh
);
}
else
{
vh
=
(
ViewHolder
)
convertView
.
getTag
();
}
Module
module
=
getItem
(
position
);
vh
.
name
.
setText
(
"name= "
+
module
.
getName
());
vh
.
version
.
setText
(
"version= "
+
module
.
getVersion
());
vh
.
versionCode
.
setText
(
"versioncode= "
+
module
.
getVersionCode
());
vh
.
description
.
setText
(
"description= "
+
module
.
getDescription
());
vh
.
cache
.
setText
(
"is from cache= "
+
module
.
isCache
());
return
convertView
;
}
private
class
ViewHolder
{
public
TextView
name
;
public
TextView
version
;
public
TextView
versionCode
;
public
TextView
description
;
public
TextView
cache
;
}
}
}
app/src/main/java/com/topjohnwu/magisk/ui/utils/Utils.java
0 → 100644
View file @
4a48f59d
package
com
.
topjohnwu
.
magisk
.
ui
.
utils
;
import
org.sufficientlysecure.rootcommands.Shell
;
import
org.sufficientlysecure.rootcommands.command.SimpleCommand
;
import
java.io.IOException
;
import
java.util.concurrent.TimeoutException
;
public
class
Utils
{
public
static
String
executeCommand
(
String
...
commands
)
{
try
{
Shell
shell
=
Shell
.
startRootShell
();
SimpleCommand
command
=
new
SimpleCommand
(
commands
);
shell
.
add
(
command
).
waitForFinish
();
String
output
=
command
.
getOutput
();
output
=
output
.
replaceAll
(
"\n"
,
""
);
shell
.
close
();
return
output
;
}
catch
(
IOException
|
TimeoutException
e
)
{
return
""
;
}
}
}
app/src/main/res/layout/activity_main.xml
View file @
4a48f59d
...
...
@@ -4,7 +4,7 @@
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:focusableInTouchMode=
"true"
tools:context=
"
com.topjohnwu.magisk
.MainActivity"
>
tools:context=
"
.ui
.MainActivity"
>
<TextView
android:id=
"@+id/magisk_label"
...
...
app/src/main/res/layout/activity_modules.xml
View file @
4a48f59d
...
...
@@ -4,7 +4,7 @@
xmlns:tools=
"http://schemas.android.com/tools"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
tools:context=
"
com.topjohnwu.magisk
.ModulesActivity"
>
tools:context=
"
.ui
.ModulesActivity"
>
<ListView
android:id=
"@android:id/list"
...
...
app/src/test/java/com/topjohnwu/magisk/ExampleUnitTest.java
deleted
100644 → 0
View file @
bc2c63bf
package
com
.
topjohnwu
.
magisk
;
import
org.junit.Test
;
import
static
org
.
junit
.
Assert
.*;
/**
* To work on unit tests, switch the Test Artifact in the Build Variants view.
*/
public
class
ExampleUnitTest
{
@Test
public
void
addition_isCorrect
()
throws
Exception
{
assertEquals
(
4
,
2
+
2
);
}
}
\ No newline at end of file
lib/RootCommands/.gitignore
0 → 100644
View file @
4a48f59d
build/
*.iml
\ No newline at end of file
lib/RootCommands/build.gradle
0 → 100644
View file @
4a48f59d
apply
plugin:
'com.android.library'
buildscript
{
repositories
{
mavenCentral
()
}
dependencies
{
classpath
'com.android.tools.build:gradle:2.1.3'
}
}
android
{
compileSdkVersion
24
buildToolsVersion
"24.0.1"
defaultConfig
{
minSdkVersion
7
targetSdkVersion
24
}
}
lib/RootCommands/src/main/AndroidManifest.xml
0 → 100644
View file @
4a48f59d
<?xml version="1.0" encoding="utf-8"?>
<manifest
package=
"org.sufficientlysecure.rootcommands"
>
<application/>
</manifest>
\ No newline at end of file
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Mount.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
* 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
<
String
>
mFlags
;
Mount
(
File
device
,
File
path
,
String
type
,
String
flagsStr
)
{
mDevice
=
device
;
mMountPoint
=
path
;
mType
=
type
;
mFlags
=
new
HashSet
<
String
>(
Arrays
.
asList
(
flagsStr
.
split
(
","
)));
}
public
File
getDevice
()
{
return
mDevice
;
}
public
File
getMountPoint
()
{
return
mMountPoint
;
}
public
String
getType
()
{
return
mType
;
}
public
Set
<
String
>
getFlags
()
{
return
mFlags
;
}
@Override
public
String
toString
()
{
return
String
.
format
(
"%s on %s type %s %s"
,
mDevice
,
mMountPoint
,
mType
,
mFlags
);
}
}
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Remounter.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
* 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
org.sufficientlysecure.rootcommands.command.SimpleCommand
;
import
org.sufficientlysecure.rootcommands.util.Log
;
import
java.io.File
;
import
java.io.FileReader
;
import
java.io.IOException
;
import
java.io.LineNumberReader
;
import
java.util.ArrayList
;
import
java.util.Locale
;
//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 return an ArrayList of the class Mount. The class mount contains the following
* property's: device mountPoint type flags
* <p>
* These will provide you with any information you need to work with the mount points.
*
* @return <code>ArrayList<Mount></code> an ArrayList of the class Mount.
* @throws Exception if we cannot return the mount points.
*/
protected
static
ArrayList
<
Mount
>
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
<
Mount
>
mounts
=
new
ArrayList
<
Mount
>();
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
;
}
/**
* This will take a path, which can contain the file name as well, and attempt to remount the
* underlying partition.
* <p>
* 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 <code>boolean</code> 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
<
Mount
>
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
;
}
}
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/RootCommands.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
final
String
TAG
=
"RootCommands"
;
public
static
boolean
DEBUG
=
false
;
public
static
int
DEFAULT_TIMEOUT
=
10000
;
/**
* 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
;
}
}
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Shell.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
* 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
org.sufficientlysecure.rootcommands.command.Command
;
import
org.sufficientlysecure.rootcommands.util.Log
;
import
org.sufficientlysecure.rootcommands.util.RootAccessDeniedException
;
import
org.sufficientlysecure.rootcommands.util.Utils
;
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
;
public
class
Shell
implements
Closeable
{
private
static
final
String
LD_LIBRARY_PATH
=
System
.
getenv
(
"LD_LIBRARY_PATH"
);
private
static
final
String
token
=
"F*D^W@#FGF"
;
private
final
Process
shellProcess
;
private
final
BufferedReader
stdOutErr
;
private
final
DataOutputStream
outputStream
;
private
final
List
<
Command
>
commands
=
new
ArrayList
<
Command
>();
private
boolean
close
=
false
;
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
);
}
}
};
private
Shell
(
String
shell
,
ArrayList
<
String
>
customEnv
,
String
baseDirectory
)
throws
IOException
{
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
();
}
/**
* Start root shell
*
* @param customEnv
* @param baseDirectory
* @return
* @throws IOException
*/
public
static
Shell
startRootShell
(
ArrayList
<
String
>
customEnv
,
String
baseDirectory
)
throws
IOException
{
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
<
String
>();
}
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
{
return
startRootShell
(
null
,
null
);
}
/**
* Start default sh shell
*
* @param customEnv
* @param baseDirectory
* @return
* @throws IOException
*/
public
static
Shell
startShell
(
ArrayList
<
String
>
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
<
String
>
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
);
}
/**
* 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
();
Log
.
d
(
RootCommands
.
TAG
,
"Closing shell"
);
shellProcess
.
waitFor
();
out
.
close
();
return
;
}
else
{
Thread
.
sleep
(
50
);
}
}
}
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
();
stdOutErr
.
close
();
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
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/SystemCommands.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
*/
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) {
// // }
// // }
// // }
// }
}
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/Toolbox.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
* 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
android.os.StatFs
;
import
android.os.SystemClock
;
import
org.sufficientlysecure.rootcommands.command.Command
;
import
org.sufficientlysecure.rootcommands.command.ExecutableCommand
;
import
org.sufficientlysecure.rootcommands.command.SimpleCommand
;
import
org.sufficientlysecure.rootcommands.util.BrokenBusyboxException
;
import
org.sufficientlysecure.rootcommands.util.Log
;
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
;
/**
* 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
* <p>
* 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
{
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
;
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
* <p>
* (commands: id)
*
* @return true if user has given root access
* @throws IOException
* @throws TimeoutException
* @throws BrokenBusyboxException
*/
public
boolean
isRootAccessGiven
()
throws
TimeoutException
,
IOException
{
SimpleCommand
idCommand
=
new
SimpleCommand
(
"id"
);
shell
.
add
(
idCommand
).
waitForFinish
();
return
idCommand
.
getOutput
().
contains
(
"uid=0"
);
}
/**
* This method can be used to kill a running process
* <p>
* (commands: ps, kill)
*
* @param processName name of process to kill
* @return <code>true</code> if process was found and killed successfully
* @throws IOException
* @throws TimeoutException
* @throws BrokenBusyboxException
*/
public
boolean
killAll
(
String
processName
)
throws
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
();
return
killCommand
.
getExitCode
()
==
0
;
}
else
{
Log
.
d
(
RootCommands
.
TAG
,
"No pid found! Nothing was killed!"
);
return
false
;
}
}
/**
* Kill a running executable
* <p>
* 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
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 <code>true</code> if process was found
* @throws IOException
* @throws BrokenBusyboxException
* @throws TimeoutException (Could not determine if the process is running)
*/
public
boolean
isProcessRunning
(
String
processName
)
throws
TimeoutException
,
IOException
{
PsCommand
psCommand
=
new
PsCommand
(
processName
);
shell
.
add
(
psCommand
).
waitForFinish
();
// if pids are available process is running!
return
!
psCommand
.
getPids
().
isEmpty
();
}
/**
* Checks if binary is running
*
* @param binaryName
* @return
* @throws BrokenBusyboxException
* @throws TimeoutException
* @throws IOException
*/
public
boolean
isBinaryRunning
(
String
binaryName
)
throws
TimeoutException
,
IOException
{
return
isProcessRunning
(
ExecutableCommand
.
EXECUTABLE_PREFIX
+
binaryName
+
ExecutableCommand
.
EXECUTABLE_SUFFIX
);
}
/**
* @param file String that represent the file, including the full path to the file and its name.
* @return File permissions as String, for example: 777, returns null on error
* @throws IOException
* @throws TimeoutException
* @throws BrokenBusyboxException
*/
public
String
getFilePermissions
(
String
file
)
throws
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
TimeoutException
,
IOException
{
Log
.
d
(
RootCommands
.
TAG
,
"Set permissions of "
+
file
+
" to "
+
permissions
);
SimpleCommand
chmodCommand
=
new
SimpleCommand
(
"chmod "
+
permissions
+
" "
+
file
);
shell
.
add
(
chmodCommand
).
waitForFinish
();
return
chmodCommand
.
getExitCode
()
==
0
;
}
/**
* 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
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
* @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
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
;
}
/**
* Shutdown or reboot device. Possible actions are REBOOT_HOTREBOOT, REBOOT_REBOOT,
* REBOOT_SHUTDOWN, REBOOT_RECOVERY
*
* @throws IOException
* @throws TimeoutException
* @throws BrokenBusyboxException
*/
public
void
reboot
(
int
action
)
throws
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!"
);
}
}
}
/**
* 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
TimeoutException
,
IOException
{
FileExistsCommand
fileExistsCommand
=
new
FileExistsCommand
(
file
);
shell
.
add
(
fileExistsCommand
).
waitForFinish
();
return
fileExistsCommand
.
isFileExists
();
}
/**
* 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
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
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
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
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.
* <p>
* 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 <code>boolean</code> 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...
*
* @return <code>String</code> 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
<
Mount
>
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
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
();
}
}
/**
* This command class gets all pids to a given process name
*/
private
class
PsCommand
extends
Command
{
private
String
processName
;
private
ArrayList
<
String
>
pids
;
private
String
psRegex
;
private
Pattern
psPattern
;
public
PsCommand
(
String
processName
)
{
super
(
"ps"
);
this
.
processName
=
processName
;
pids
=
new
ArrayList
<
String
>();
/**
* regex to get pid out of ps line, example:
*
* <pre>
* root 24736 1 12140 584 ffffffff 40010d14 S /data/data/org.adaway/files/blank_webserver
* ^\\S \\s ([0-9]+) .* processName $
* </pre>
*/
psRegex
=
"^\\S+\\s+([0-9]+).*"
+
Pattern
.
quote
(
processName
)
+
"$"
;
psPattern
=
Pattern
.
compile
(
psRegex
);
}
public
ArrayList
<
String
>
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
)
{
}
}
/**
* 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
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:
*
* <pre>
* 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}) .* $
* </pre>
*/
permissionRegex
=
"^.*?(\\S{10}).*$"
;
permissionPattern
=
Pattern
.
compile
(
permissionRegex
);
/**
* regex to get symlink
*
* <pre>
* -> /proc/self/fd/0
* ^.*?\\-\\> \\s+ (.*) $
* </pre>
*/
symlinkRegex
=
"^.*?\\-\\>\\s+(.*)$"
;
symlinkPattern
=
Pattern
.
compile
(
symlinkRegex
);
}
public
String
getSymlink
()
{
return
symlink
;
}
public
String
getPermissions
()
{
return
permissions
;
}
/**
* 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
)
{
}
}
/**
* 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
)
{
}
}
public
abstract
class
WithPermissions
{
abstract
void
whileHavingPermissions
();
}
}
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/Command.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
* 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
org.sufficientlysecure.rootcommands.RootCommands
;
import
org.sufficientlysecure.rootcommands.Shell
;
import
org.sufficientlysecure.rootcommands.util.BrokenBusyboxException
;
import
org.sufficientlysecure.rootcommands.util.Log
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.util.concurrent.TimeoutException
;
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
*
* @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
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/ExecutableCommand.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.annotation.SuppressLint
;
import
android.content.Context
;
import
android.os.Build
;
import
java.io.File
;
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!
* <p>
* 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!
* <p>
* 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
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleCommand.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleExecutableCommand.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/BrokenBusyboxException.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
);
}
}
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/Log.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
);
}
}
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/RootAccessDeniedException.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
);
}
}
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/UnsupportedArchitectureException.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
);
}
}
lib/RootCommands/src/main/java/org/sufficientlysecure/rootcommands/util/Utils.java
0 → 100644
View file @
4a48f59d
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
* 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
org.sufficientlysecure.rootcommands.RootCommands
;
import
java.io.File
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Map
;
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.
* <p>
* 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().
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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
<
String
>
customAddedEnv
,
String
baseDirectory
)
throws
IOException
{
Map
<
String
,
String
>
environment
=
System
.
getenv
();
String
[]
envArray
=
new
String
[
environment
.
size
()
+
(
customAddedEnv
!=
null
?
customAddedEnv
.
size
()
:
0
)];
int
i
=
0
;
for
(
Map
.
Entry
<
String
,
String
>
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
;
}
}
settings.gradle
View file @
4a48f59d
include
':app'
include
':app'
,
':lib:RootCommands'
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment