1>fdisk_shell.sh文件需在linux下用vim生成 否则会出很多意想不到的问题,
若在win下生成该文件在导入linux需用busybox dos2unix fdisk_shell.sh命令转化为linux文件。
2>整体思路:在应用层调用fdisk_shell.sh脚本实现格式化功能。
3>实践中发现,应用层通过system方法调用的. /fdisk_shell.sh语句只能执行fdisk_shell.sh脚本中的简单语句
像echo命令,但是system/bin下的其他命令及BUSYBOX命令是无法执行的;这行命令的执行需要ROOT权限,其实
system方法是可以实现ROOT身份的转换,但只能执行具体的命令,或是运行脚本的命令system(". /fdisk_shell.sh");
但是要想运行脚本中的busybox命令是不成功的,只有另辟蹊径;
最终思路是在init.rc中开启一个守护进程rootexec,实现脚本的执行工作,应用层通过进程间通信,实现脚本的执行。
应用层通过JNI调用startRootExec()方法,startRootExec()方法通过bind机制使rootexec实现执行脚本的任务。
也可以用非正式的方法shocket,这样可以避开JNI层的调用;
4>重要代码分析:
\root\cmds\rootexec\RootExecService.cpp:
if (-1 == status)
{
ALOGD("system error!");
return 0;
}
else
{
ALOGD("exit status value = [0x%x]\n", status);
if (WIFEXITED(status))
{
if (0 == WEXITSTATUS(status))
{
ALOGD("run shell script successfully.\n");
return 1;
}
else
{
ALOGD("run shell script fail, script exit code: %d\n", WEXITSTATUS(status));
return 0;
}
}
else
{
ALOGD("exit status = [%d]\n", WEXITSTATUS(status));
return 0;
}
}
此段代码是检测system方法执行脚本完成情况,弱弱的讲讲system方法的返回值的问题:
system函数对返回值的处理,涉及3个阶段:
阶段1:创建子进程等准备工作。如果失败,返回-1。
阶段2:调用/bin/sh拉起shell脚本,如果拉起失败或者shell未正常执行结束(参见备注1),
原因值被写入到status的低8~15比特位中。system的man中只说明了会写了127这个值,但实测发现还会写126等值。
阶段3:如果shell脚本正常执行结束,将shell返回值填到status的低8~15比特位中。
备注1:
只要能够调用到/bin/sh,并且执行shell过程中没有被其他信号异常中断,都算正常结束。
比如:不管shell脚本中返回什么原因值,是0还是非0,都算正常执行结束。即使shell脚本不存在或没有执行权限,
也都算正常执行结束。
如果shell脚本执行过程中被强制kill掉等情况则算异常结束。
如何判断阶段2中,shell脚本是否正常执行结束呢?系统提供了宏:WIFEXITED(status)。如果WIFEXITED(status)为真,
则说明正常结束。
如何取得阶段3中的shell返回值?你可以直接通过右移8bit来实现,
但安全的做法是使用系统提供的宏:WEXITSTATUS(status)。
由于我们一般在shell脚本中会通过返回值判断本脚本是否正常执行,如果成功返回0,失败返回正数。
所以综上,判断一个system函数调用shell脚本是否正常结束的方法应该是如下3个条件同时成立:
(1)-1 != status
(2)WIFEXITED(status)为真
(3)0 == WEXITSTATUS(status)
注意:
根据以上分析,当shell脚本不存在、没有执行权限等场景下时,以上前2个条件仍会成立,
此时WEXITSTATUS(status)为127,126等数值。
所以,我们在shell脚本中不能将127,126等数值定义为返回值,否则无法区分中是shell的返回值,
还是调用shell脚本异常的原因值。shell脚本中的返回值最好多1开始递增。
脚本执行成功返回0,失败1;
\myTest\jni\fdisk.cpp:
static jstring run(JNIEnv *env,jobject obj,jstring comd,jint num)
{
char* tmpstr1 = "successed";
char* tmpstr2 = "failed";
jstring jstr1 = env->NewStringUTF(tmpstr1);
jstring jstr2 = env->NewStringUTF(tmpstr2);
ALOGE("fdiskShelljni.so");
if(startRootExec(2,2))
{
ALOGE("startRootExec success!\n");
return jstr1;
}else{
ALOGE("startRootExec failed!\n");
return jstr2;
}
}
此段代码为jni的主体,作用很简单,执行startRootExec()方法,成功返回successed,失败返回failed;
改文件中还实现了其他方法,应用层调用JNI库所需。
\myTest\src\com\fdisk\FdiskActivity.java:
改文件为测试程序。格式化一个500G的硬盘需要很长时间,这么,漫长的等待,花儿会不会谢呢?只有在另外的线程完成,
主线程只负责启动thread,在thread中实现FLAG状态的改变,
脚本执行成功JNI会返回successed,此时将FLAG置1,失败返回failed,将FLAG置0,;
主线程根据值得变化判断格式化是否成功。开始将FLAG置2,处理很好,读者自己琢磨;
fdisk_shell.sh:
这个脚本所要完成的工作就是判断目前硬盘(/dev/block/sda)里的分区情况,然后去删除分区,再重新创建一个sda1的
分区。该脚本花了我很长时间,但功能很完善,除了可以删除主分区还能删除扩展分区,当然这个工作可以在RootExecService.cpp中用删除
表头的方法实现,我认为这样更好;
/system/fdisk/1.0保存的是整个硬盘的分区情况,共有几个分区;
/system/fdisk/1.1保存的是整个硬盘的主分区情况,共有几个主分区;
/system/fdisk/1.2保存的是整个硬盘的扩展分区情况,共有几个扩展分区;
这3个文件保存的信息是给后面命令提供参数依据;
如果你很熟悉FDISK命令的话,就会明白为什么在删除最后一个分区的时候要另外echo?
也会明白要umount vfat格式自动挂载的8_*目录的原因,否则是mount不上的;
导致mount命令失败的原因大致为二:1,格式化未成功;2,之前已经mount。
所以该脚本为了保险用力的umount.
这脚本花的精力很多,读者若是觉得有更好的方法可以联系:424758702(qq)
5>整个代码的编译:
myTest文件要放在android4.2/package/app/下,jni库的生成和测试apk的生成只要在myTest文件夹下mm即可。
生成的so库会出现android/out/target/product/win_k70/system/lib/下,改库需要的librootexec.so,如果你完全按root目录
下的Read文档做了就会在android/out/target/product/win_k70/system/lib/下生成;librootexec.so这个库和
android/out/target/product/win_k70/system/bin/下的rootexec二进制文件我是自动打包进固件里的,
会出现在板子的system/lib和system/bin里的(如果你完全按文档做了);
我的邮件
[email protected]
文档很粗糙,愿君自琢磨。
源码不提供,思路很重要(保密,公司要求我不得不服)。