一、SystemMix接口说明
在android/frameworks/base/swextend/systemmix目录下,全志提供了一套用于访问底层高权限信息的接口,用户可以参照里面的做法来扩展SystemMix类的功能。
1. systemmix目录结构:
2. 该机制使用了Android上使用广泛的 客户端<--->服务端 机制去实现,调度流程如下:
3. 该SystemMix.java文件为用户提供了如下几个示例接口:
/**
* mount device or other
*/
public static int Mount(String source, String mountPoint, String fs, int flags, String options){
return nativeMount(source, mountPoint, fs, flags, options);
}
/**
* umount device or other
*/
public static int Umount(String mountPoint){
return nativeUmount(mountPoint);
}
/**
* get a property's value
*/
public static String getProperty(String key){
return nativeGetProperty(key);
}
/**
* set a property's value
*/
public static void setProperty(String key, String value){
if(key != null && value != null){
nativeSetProperty(key,value);
}
}
/**
* get command para in /proc/cmdline
*/
public static String getCmdPara(String name){
HashMap<String, String> paraMap = mapPara();
return paraMap.get(name);
}
@SuppressWarnings("null")
private static HashMap<String, String> mapPara(){
HashMap<String, String> paraMap = new HashMap<String, String>();
String cmdline = getCmdLine();
Log.d(TAG,"getCmdLine = " + cmdline);
if(cmdline != null){
String[] list = cmdline.split(" ");
if(list != null){
for(int i = 0; i < list.length; i++){
String[] map = list[i].split("=");
if(map == null || map.length != 2){
continue;
}
paraMap.put(map[0], map[1]);
}
}
}
return paraMap;
}
private static String getCmdLine(){
byte[] desData = new byte[256];
int ret = nativeGetFileData(desData, desData.length, "/proc/cmdline");
String str = null;
if(ret > 0){
str = new String(desData);
}
return str;
}
用户在扩展该SystemMix接口的时候,可以参考getCmdPara()方法的调度流程,同时对java端、jni端,client端,server端做出相应的扩展。
二、扩展SystemMix接口,让app执行shell命令
在SystemMix中添加runShell操作接口,为app提供root权限来执行shell命令。
1. SystemMix接口扩展
我们按照java端(SystemMix.java)、jni端(com_softwinner_SystemMix.cpp),client端(ISystemMixService.h, ISystemMixService.cpp),server端(SystemMixService.h, SystemMixService.cpp)的顺序修改android/frameworks/base/swextend/systemmix目录下的文件,增加runShell操作接口。
关键代码如下:
SystemMix.java
private static native String nativeRunShell(String cmd);
/**
* run shell command
*/
public static String runShell(String cmd){
return nativeRunShell(cmd);
}
com_softwinner_SystemMix.cpp
static jstring runShell_native(JNIEnv *env, jobject clazz, jstring jcmd) {
jstring value = NULL;
if (systemmixService == NULL || jcmd == NULL) {
throw_NullPointerException(env, "systemmix service has not start, or shell cmd is null!");
}
const char *cmd = env->GetStringUTFChars(jcmd, NULL);
char *cvalue = new char[MAX_BUFFER_SIZE];
if (cvalue == NULL) {
env->ReleaseStringUTFChars(jcmd, cmd);
throw_NullPointerException(env, "runshell_native() fail to allocate memory for value");
}
int ret = systemmixService->runShell(cmd, cvalue);
value = env->NewStringUTF(cvalue);
if (value == NULL) {
ALOGE("Fail in creating java string with %s", cvalue);
}
delete[] cvalue;
env->ReleaseStringUTFChars(jcmd, cmd);
return value;
}
static JNINativeMethod method_table[] = {
{ "nativeInit", "()V", (void*)init_native},
{ "nativeSetProperty", "(Ljava/lang/String;Ljava/lang/String;)I", (void*)setProperty_native },
{ "nativeGetProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getProperty_native },
{ "nativeGetFileData", "([BILjava/lang/String;)I", (void*)getFileData_native },
{ "nativeMount", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)I", (void*)mount_native },
{ "nativeUmount", "(Ljava/lang/String;)I", (void*)umount_native },
{ "nativeRunShell", "(Ljava/lang/String;)Ljava/lang/String;", (void*)runShell_native },
};
ISystemMixService.h
// defind max buffer size for runshell
#define MAX_BUFFER_SIZE 1024
ISystemMixService.cpp
enum {
GET_PROPERTY = IBinder::FIRST_CALL_TRANSACTION,
GET_FILEDATA,
MOUNT,
UMOUNT,
RUN_SHELL,
SET_PROPERTY = IBinder::LAST_CALL_TRANSACTION
};
int runShell(const char *cmd, char *result) {
if (DEBUG) {
ALOGV("ISystemMixService::runShell() cmd = %s", cmd);
}
Parcel data, reply;
data.writeInterfaceToken(ISystemMixService::getInterfaceDescriptor());
data.writeCString(cmd);
remote()->transact(RUN_SHELL, data, &reply);
int ret = reply.readInt32();
ALOGD("ISystemMixService::runShell() ret = %d", ret);
const char* rpy = reply.readCString();
int len = (int)strlen(rpy);
if(MAX_BUFFER_SIZE > len){
strcpy(result, rpy);
}else{
strncpy(result, rpy, MAX_BUFFER_SIZE);
result[MAX_BUFFER_SIZE - 1] = '\0';
}
//ALOGD("ISystemMixService::runShell() result=\n%s rpy=\n%s", result, rpy);
return ret;
}
IMPLEMENT_META_INTERFACE(SystemMixService, "com.softwinner.ISystemMixService");
status_t BnSystemMixService::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){
switch(code){
case SET_PROPERTY:{
CHECK_INTERFACE(ISystemMixService, data, reply);
const char *key = data.readCString();
const char *value = data.readCString();
reply->writeInt32(setProperty(key, value));
return NO_ERROR;
}break;
case GET_PROPERTY:{
CHECK_INTERFACE(ISystemMixService, data, reply);
const char *key = data.readCString();
char *value = new char[PROPERTY_VALUE_MAX];
ALOGD("BnSystemMixService::getProperty() key=%s", key);
int ret = getProperty(key, value);
ALOGD("BnSystemMixService::getProperty() value=%s, ret=%d", value, ret);
reply->writeInt32(ret);
if(ret > 0){
reply->writeCString(value);
}
delete[] value;
return NO_ERROR;
}break;
case GET_FILEDATA:{
CHECK_INTERFACE(ISystemMixService, data, reply);
int count = data.readInt32();
const char *filePath = data.readCString();
int8_t *d = new int8_t[count];
memset(d, 0, count * sizeof(int8_t));
int ret = getFileData(d, count, filePath);
ALOGD("BnSystemMixService::getFileData() read data is %s", d);
reply->writeInt32(ret);
reply->write(d, count * sizeof(int8_t));
delete[] d;
return NO_ERROR;
}break;
case MOUNT:{
CHECK_INTERFACE(ISystemMixService, data, reply);
const char *src = data.readCString();
const char *mountpoint = data.readCString();
const char *fs = data.readCString();
unsigned int flag = data.readInt32();
const char *options = data.readCString();
reply->writeInt32(mountDev(src, mountpoint, fs, flag, options));
return NO_ERROR;
}break;
case UMOUNT:{
CHECK_INTERFACE(ISystemMixService, data, reply);
const char *umountpoint = data.readCString();
reply->writeInt32(umountDev(umountpoint));
return NO_ERROR;
}break;
case RUN_SHELL: {
CHECK_INTERFACE(ISystemMixService, data, reply);
const char *cmd = data.readCString();
char *result = new char[MAX_BUFFER_SIZE];
memset(result, 0, MAX_BUFFER_SIZE);
reply->writeInt32(runShell(cmd, result));
//ALOGD("BnSystemMixService::runShell() result is %s", result);
reply->writeCString(result);
delete[] result;
return NO_ERROR;
}break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
SystemMixService.h
class SystemMixService:public BnSystemMixService{
public:
static void instantiate();
virtual int getProperty(const char *key, char *value);
virtual int setProperty(const char *key, const char *value);
virtual int getFileData(int8_t *data, int count, const char *filePath);
virtual int mountDev(const char *src, const char *mountPoint, const char *fs,
unsigned int flag, const char *options);
virtual int umountDev(const char *mountPoint);
virtual int runShell(const char *cmd, char *result);
private:
SystemMixService();
virtual ~SystemMixService();
};
SystemMixService.cpp
int SystemMixService::runShell(const char *cmd, char *result) {
if (DEBUG) {
ALOGD("SystemMixService::runshell() cmd = %s", cmd);
}
char buffer[MAX_BUFFER_SIZE] = {0};
FILE *fp;
int ret = 0;
// write command to buffer, %s 2>&1
snprintf(buffer, sizeof(buffer), "%s\n", cmd);
fp = popen(buffer, "r");
if (fp != NULL){
int remainder = MAX_BUFFER_SIZE;
int len = 0;
memset(buffer, 0, MAX_BUFFER_SIZE);
while (fgets(buffer, MAX_BUFFER_SIZE, fp) != NULL){
len = strlen(buffer);
if (remainder > len){
strcat(result, buffer);
remainder -= len;
}else{
if (remainder > 1){
strncat(result, buffer, remainder - 1);
remainder = 0;
break;
}
}
//ALOGD("%s", buffer);
}
ret = pclose(fp);
if (ret == -1){
ALOGD("SystemMixService::runshell() pclose() failed\n");
return -1;
}
if (WIFEXITED(ret)){
ALOGD("SystemMixService::runshell() subprocess exited, exit code: %d\n", WEXITSTATUS(ret));
if (0 != WEXITSTATUS(ret)){
ALOGD("SystemMixService::runshell() command failed: %s\n", strerror(WEXITSTATUS(ret)));
return WEXITSTATUS(ret);
}
}else{
ALOGD("SystemMixService::runshell() subprocess exit failed\n");
return -1;
}
}else{
ALOGD("SystemMixService::runshell() popen() %s error\n", cmd);
return -1;
}
//ALOGD("SystemMixService::runshell() result = %s", result);
return 0;
}
2. 启动文件说明
SystemMix接口实现的关键在于系统启动的时候以root身份加载了systemmix服务,详见android/device/softwinner/wing-common/init.rc文件:
# start system mix service
service property /system/bin/systemmixservice
class main
user root
group root audio camera graphics inet net_bt net_bt_admin net_raw
ioprio rt 4
oneshot
3. app项目中的使用方法
runShell操作接口扩展完毕后重新编译Android系统,会在android/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/目录下生成classes.jar,classes-full-debug.jar(带调试信息)文件。
拷贝classes.jar库文件到你AndroidStudio工程的app\libs目录下(如果没有libs文件夹,请新建一个),然后点击Project Structure,
在弹出的界面上选择Dependencies选项卡,
点击右侧+按钮,在弹出的菜单中选择 2 Jar dependency,
弹出Select Path界面,选择libs目录下的classes.jar文件,点击OK,将库文件加入到工程中。
如果提示库文件冲突,导入失败,请打开classes.jar包,删除有冲突的目录。可以只保留META-INF和com目录,删除com下除softwinner以外的其它目录。
在你的代码中引用SystemMix:import com.softwinner.SystemMix,在需要执行shell命令的地方使用SystemMix.runShell("shell command")即可,shell command为Android所支持的shell命令。
提示:可以使用am start启动app,使用pm install静默安装apk。
代码示例(显示根目录下的文件结构):
String cmd, str;
cmd = "ls -l /";
str = SystemMix.runShell(cmd);
Log.d(TAG, String.format("Run shell result:\n %s", str));