Commit 14745317 authored by Mygod's avatar Mygod

Implement native mode without binary copying

Plugins that support this mode only need to implement `NativePluginProvider.getExecutable`.
parent 9e6948ca
......@@ -159,7 +159,7 @@ trait BaseService extends Service {
Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES))
plugin = new PluginConfiguration(profile.plugin).selectedOptions
pluginPath = PluginManager.initPlugin(plugin.id)
pluginPath = PluginManager.init(plugin)
}
def startRunner(profile: Profile) {
......
......@@ -5,6 +5,8 @@ import java.io.{File, FileNotFoundException, FileOutputStream, IOException}
import android.content.pm.PackageManager
import android.content.{BroadcastReceiver, ContentResolver, Intent}
import android.net.Uri
import android.os.Bundle
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.CloseUtils.autoClose
import com.github.shadowsocks.utils.{Commandline, IOUtils}
......@@ -41,11 +43,11 @@ object PluginManager {
// the following parts are meant to be used by :bg
@throws[Throwable]
def initPlugin(id: String): String = {
if (id.isEmpty) return null
def init(options: PluginOptions): String = {
if (options.id.isEmpty) return null
var throwable: Throwable = null
try initNativePlugin(id) match {
try initNative(options) match {
case null =>
case path => return path
} catch {
......@@ -56,44 +58,61 @@ object PluginManager {
// add other plugin types here
throw if (throwable == null)
new FileNotFoundException(app.getString(com.github.shadowsocks.R.string.plugin_unknown, id)) else throwable
throw if (throwable == null) new FileNotFoundException(
app.getString(com.github.shadowsocks.R.string.plugin_unknown, options.id)) else throwable
}
@throws[IOException]
@throws[IndexOutOfBoundsException]
@throws[AssertionError]
private def initNativePlugin(id: String): String = {
private def initNative(options: PluginOptions): String = {
val providers = app.getPackageManager.queryIntentContentProviders(
new Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(id)), 0)
new Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(options.id)), 0)
assert(providers.length == 1)
val builder = new Uri.Builder()
val uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(providers(0).providerInfo.authority)
.build()
val cr = app.getContentResolver
val cursor = cr.query(builder.build(), Array(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE),
null, null, null)
if (cursor != null) {
var initialized = false
val pluginDir = new File(app.getFilesDir, "plugin")
def entryNotFound() = throw new IndexOutOfBoundsException("Plugin entry binary not found")
if (!cursor.moveToFirst()) entryNotFound()
IOUtils.deleteRecursively(pluginDir)
if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory")
val pluginDirPath = pluginDir.getAbsolutePath + '/'
val list = new ListBuffer[String]
do {
val path = cursor.getString(0)
val file = new File(pluginDir, path)
assert(file.getAbsolutePath.startsWith(pluginDirPath))
autoClose(cr.openInputStream(builder.path(path).build()))(in =>
autoClose(new FileOutputStream(file))(out => IOUtils.copy(in, out)))
list += Commandline.toString(Array("chmod", cursor.getString(1), file.getAbsolutePath))
if (path == id) initialized = true
} while (cursor.moveToNext())
if (!initialized) entryNotFound()
Shell.SH.run(list)
new File(pluginDir, id).getAbsolutePath
} else null
try initNativeFast(cr, options, uri) catch {
case t: Throwable =>
t.printStackTrace()
Log.w("PluginManager", "Initializing native plugin fast mode failed. Falling back to slow mode.")
initNativeSlow(cr, options, uri)
}
}
private def initNativeFast(cr: ContentResolver, options: PluginOptions, uri: Uri): String = {
val out = new Bundle()
out.putString(PluginContract.EXTRA_OPTIONS, options.id)
val result = cr.call(uri, PluginContract.METHOD_GET_EXECUTABLE, null, out).getString(PluginContract.EXTRA_ENTRY)
assert(new File(result).canExecute)
result
}
private def initNativeSlow(cr: ContentResolver, options: PluginOptions, uri: Uri): String =
cr.query(uri, Array(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE), null, null, null) match {
case null => null
case cursor =>
var initialized = false
val pluginDir = new File(app.getFilesDir, "plugin")
def entryNotFound() = throw new IndexOutOfBoundsException("Plugin entry binary not found")
if (!cursor.moveToFirst()) entryNotFound()
IOUtils.deleteRecursively(pluginDir)
if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory")
val pluginDirPath = pluginDir.getAbsolutePath + '/'
val list = new ListBuffer[String]
do {
val path = cursor.getString(0)
val file = new File(pluginDir, path)
assert(file.getAbsolutePath.startsWith(pluginDirPath))
autoClose(cr.openInputStream(uri.buildUpon().path(path).build()))(in =>
autoClose(new FileOutputStream(file))(out => IOUtils.copy(in, out)))
list += Commandline.toString(Array("chmod", cursor.getString(1), file.getAbsolutePath))
if (path == options.id) initialized = true
} while (cursor.moveToNext())
if (!initialized) entryNotFound()
Shell.SH.run(list)
new File(pluginDir, options.id).getAbsolutePath
}
}
......@@ -30,6 +30,14 @@ public final class PluginContract {
*/
public static final String ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP";
/**
* The lookup key for a string that provides the plugin entry binary.
*
* Example: "/data/data/com.github.shadowsocks.plugin.obfs_local/lib/libobfs-local.so"
*
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_ENTRY"
*/
public static final String EXTRA_ENTRY = "com.github.shadowsocks.plugin.EXTRA_ENTRY";
/**
* The lookup key for a string that provides the whole command as a string.
*
......@@ -61,6 +69,8 @@ public final class PluginContract {
*/
public static final String METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config";
static final String METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable";
/**
* Relative to the file to be copied. This column is required.
*
......
......@@ -3,7 +3,7 @@ package com.github.shadowsocks.plugin
import android.content.{ContentProvider, ContentValues}
import android.database.{Cursor, MatrixCursor}
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.os.{Bundle, ParcelFileDescriptor}
/**
* Base class for a native plugin provider. A native plugin provider offers read-only access to files that are required
......@@ -52,12 +52,30 @@ abstract class NativePluginProvider extends ContentProvider {
result
}
/**
* Returns executable entry absolute path. This is used if plugin is sharing UID with the host.
*
* Default behavior is throwing UnsupportedOperationException. If you don't wish to use this feature, use the default
* behavior.
*
* @return Absolute path for executable entry.
*/
def getExecutable: String = throw new UnsupportedOperationException
def openFile(uri: Uri): ParcelFileDescriptor
override def openFile(uri: Uri, mode: String): ParcelFileDescriptor = {
assert(mode == "r")
openFile(uri)
}
override def call(method: String, arg: String, extras: Bundle): Bundle = method match {
case PluginContract.METHOD_GET_EXECUTABLE =>
val out = new Bundle()
out.putString(PluginContract.EXTRA_ENTRY, getExecutable)
out
case _ => super.call(method, arg, extras)
}
// Methods that should not be used
override def update(uri: Uri, values: ContentValues, selection: String, selectionArgs: Array[String]): Int = ???
override def insert(uri: Uri, values: ContentValues): Uri = ???
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment