概述
在前段时间的工作中,需要开发一个开机自动启动的脚本,现把开发过程记录一下
主要框架
编写一个可以开机自动启动的脚本,方法就是通过rc文件,在boot_complete=1时,去启动这个服务,那么,可以先基于以上思路,创建实现脚本所需要的文件。
通常来说,我这个脚本是要放在vendor分区的,因此将脚本放到vendor目录下,参考其他的脚本,创建3个空的文件如下:
multi_tpinsmod/ //脚本文件夹
├── Android.bp //bp文件,用于放置编译参数
├── multi_tpinsmod.cpp //脚本的代码实现
└── multi_tpinsmod.rc //rc文件,用于启动脚本
0 directories, 3 files
接下来,我们就来实现如上框架。
脚本编写
脚本的实现方式有很多,包括c/c++,shell等,基于简单高效的原则,shell是比较好的选择,但是对于Android的开机自启动脚本,个人建议是选择c/c++来编写,如果是用于调试的手动执行脚本,可以使用shell来写,主要原因如下:
- Android中有许多权限问题,而以shell脚本来说,会有许多的selinux权限限制(并且许多权限还无法绕过),如果是开机自启动的脚本来说,我们并没有adb那样的权限,因此很容易导致运行失败。
- 手动执行脚本,我们可以通过su和setenforce 0来临时获取权限,且shell脚本易于修改。
由于本次是需要进行开机启动的,那么这里笔者选择使用了c++来实现脚本,代码主体不多赘述,依个人实现。
Android.bp
脚本的主要代码编写好后,Android.bp的内容如下:
cc_binary { //表示将脚本编译为bin文件
name: "multi_tpinsmod", //模块名字
init_rc: ["multi_tpinsmod.rc"], //模块关联的rc文件
relative_install_path: "hw", //是否放在hw路径下
vendor:true, //表示编译到vendor分区
cflags: [ //flag,用于忽略一下warning
"-Wall",
"-Wextra",
"-Werror",
],
shared_libs: [ //需要使用的共享库
"libbase",
"liblog",
"libprocessgroup",
"libcutils",
"libutils",
],
srcs: [ //源文件
"multi_tpinsmod.cpp",
],
}
rc编写
rc文件的编写则比较简单了,参考如下
on property:sys.boot_completed=1 //在开机启动完成时的动作,这里是启动服务
start multi_tpinsmod
service multi_tpinsmod /vendor/bin/hw/multi_tpinsmod //定义了一个服务,和该服务的执行文件路径
disabled //表示不随class自动启动,需要手动启动
user root //用户
group shell root //分属的用户组
oneshot //表示服务退出后不重新启动
capabilities SYS_MODULE
rc文件的参考如上,其余rc,可以参考网上资料自行学习。
编译
以上代码都准备好了以后,就可以进行编译了,编译可以通过mm命令直接在源码路径下执行,也可以将其配置到方案里面编译整个sdk。
一. 调试可以在源码目录下通过mm编译,执行mm需要先在top目录执行过lunch等
二.在sdk里面编译的方法如下:
- 在方案目录,找一个地方,增加PACKAGE_PRODUCT +=,参考如下
#support multi_tpinsmod
PRODUCT_PACKAGES += \
multi_tpinsmod
-
编译整个sdk,如果有编译错误,自行解决
-
编译完成后,在out目录下,搜索是否有编译出来的产物,如rc文件和bin文件
调试
编译完成后,烧录固件到机器中,然后可以通过手动执行的方式,查看脚本功能是否实现。
如果有权限报错,可以先临时关闭selinux进行调试,临时关闭selinux方式如下:
在adb中执行
setenforce 0
临时关闭selinux权限,然后手动执行脚本,确保脚本功能完成。脚本功能完成后,就可以进行脚本的权限调试了。
权限设置
更改文件的权限类型
一般来说,文件的类型在vendor目录下,是vendor_file,这种类型是没有执行权限,也就是无法通过rc来启动的,查看文件类型可以通过ls -lZ命令查看:
如图所示,通过ls -lZ查看文件的类型,如果看到类型为vendor_file,则表示需要配置文件的类型,以获取执行权限。
修改步骤如下:
- 找到配置selinux权限位置,一般来说,可以通过在Android目录下执行如下命令来获取selinux的配置目录,
get_build_var BOARD_SEPOLICY_DIRS
- 找到目录后,如笔者的配置位置在
device/softwinner/common/sepolicy/vendor
- 在目录下,先新建一个与脚本名字相同的te文件,如笔者的脚本名为A,那么可以新建一个文件A_default.te的文件
- 在新建的te文件中,编写如下:
表示新建一个名字为multi_tpinsmod的domian域
- 在同目录下的file_contexts中,增加如下
其中,前面为bin文件的路径,后面为固件格式,中间的为刚才新建的multi_tpinsmod_defalut.te中的执行类型。
- 完成后,重新编译固件,烧录固件,再次查看bin文件的类型,查看类型是否已经从vendor_file变成了自己所定义的multi_tpinsmod_exec类型。
- 临时关闭selinux权限,然后手动执行bin文件,查看串口(或者dmesg),查看是否有类似的报错信息
type=1400 audit(1629099780.612:406): avc: denied { write } for comm="tp_module_insmo" name="property_service" dev="tmpfs" ino=11726 scontext=u:r:tp_module_insmod:s0 tcontext=u:object_r:property_socket:s0 tclass=sock_file permissive=1
type=1400 audit(1629099780.612:407): avc: denied { connectto } for comm="tp_module_insmo" path="/dev/socket/property_service" scontext=u:r:tp_module_insmod:s0 tcontext=u:r:init:s0 tclass=unix_stream_socket permissive=1
type=1107 audit(1629099780.616:408): uid=0 auid=4294967295 ses=4294967295 subj=u:r:init:s0 msg='avc: denied { set } for property=persist.vendor.tp.name pid=1056 uid=0 gid=2000 scontext=u:r:tp_module_insmod:s0 tcontext=u:object_r:vendor_default_prop:s0 tclass=property_service permissive=1'
type=1400 audit(1629099780.632:409): avc: denied { sys_module } for comm="tp_module_insmo" capability=16 scontext=u:r:tp_module_insmod:s0 tcontext=u:r:tp_module_insmod:s0 tclass=capability permissive=1
type=1400 audit(1629099780.632:410): avc: denied { module_load } for comm="tp_module_insmo" path="/vendor_dlkm/lib/modules/gslX680new.ko" dev="overlay" ino=48232 scontext=u:r:tp_module_insmod:s0 tcontext=u:object_r:vendor_file:s0 tclass=system permissive=1
type=1400 audit(1629099780.832:411): avc: denied { read } for comm="tp_module_insmo" name="input" dev="sysfs" ino=8803 scontext=u:r:tp_module_insmod:s0 tcontext=u:object_r:sysfs:s0 tclass=dir permissive=1
tp_module_insmo (1056) used greatest stack depth: 9936 bytes left
type=1400 audit(1629099780.840:412): avc: denied { open } for comm="tp_module_insmo" path="/sys/class/input" dev="sysfs" ino=8803 scontext=u:r:tp_module_insmod:s0 tcontext=u:object_r:sysfs:s0 tclass=dir permissive=1
type=1400 audit(1629099780.840:413): avc: denied { read } for comm="tp_module_insmo" name="name" dev="sysfs" ino=39538 scontext=u:r:tp_module_insmod:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=1
type=1400 audit(1629099780.840:414): avc: denied { open } for comm="tp_module_insmo" path="/sys/devices/platform/soc@2900000/5002000.twi/i2c-0/0-0040/input/input7/name" dev="sysfs" ino=39538 scontext=u:r:tp_module_insmod:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=1
说明脚本已经基本完成,只剩余selinux权限问题,下一步就是配置权限问题。
配置selinux权限
手动配置权限
回到我们刚才创建的multi_tpinsmod_defalut.te,权限的配置则需要在该文件来完成。
解析selinux信息
我们拿一条selinux权限来看,例如:
type=1400 audit(1629099780.832:411): avc: denied { read } for comm="tp_module_insmo" name="input" dev="sysfs" ino=8803 scontext=u:r:tp_module_insmod:s0 tcontext=u:object_r:sysfs:s0 tclass=dir permissive=1
那么该语句表示需要配置的权限如下:
- scontext:谁需要权限,这里表示为tp_module_insmod
- tcontext:需要的是谁的权限,这里表示需要获取sysfs的权限
- tclass:需要获取的tcontext中表示的具体的类别,这里为idr
- avc: denied { read }:表示需要获取的是read权限
那么组合起来就是:tp_module_insmod需要获取sysfs中dir的read权限,那么,权限配置语句则为如下:
allow tp_module_insmod sysfs:dir read
以上语句就表示运行tp_module_insmod去读sysfs中的文件夹权限。
其他的可以自行完成。
使用audit2allow来快速配置权限
除了通过手动的方式配置权限,也可以通过使用在Android源码中的audit2allow工具来快速完成权限的配置。工具的使用步骤如下:
- 将所有相关的selinux的报错复制到一个文本中,此操作最好在selinux关闭的情况下,去完成收集,这样收集的比较全面
- 将log中的时间打印去除,语句以type=1400开头。
- 将selinux报错文件放在android的top目录中,然后执行audit2allow -i a.txt,执行结果如下:
可以看到,selinux语句被解析了出来,将其复制到对应的te文件下,重新编译,看是否会有报错(audit2allow仅进行解析,部分权限是无法获取的)。
- 重新编译,可能会有如下报错提示:nerverallow···
可以看到,编译提示,不允许vendor去获取属性的设置权限,这时可以先将报错的语句给注释掉,验证其他的权限是否还存在问题。
nerverallow处理
一般来说,出现了nerverallow是比较头疼的事情,出现的原因主要是使用了google不允许的权限导致的,那么解决办法一般有2个:
- 在出现报错的地方,强行注释掉google不允许的权限,这样可能会造成google的gms测试无法通过
- 通过其他办法绕过该权限,如vendor访问system是没办法实现的,那就可以将需要访问的资源再编译一份放到vendor
prop的设置
在上面出现的vendor_default_prop是可以通过其他办法进行绕过的,一般来说google不允许一个服务区获取所有的vendor的属性设置权限,但是允许获取特定的属性组的权限,以上述的属性设置为例,设置方法如下:
- 获取具体需要进行设置的属性:需要在代码中查看:
persist.vendor.tp.name
persist.vendor.tp.path
可以找到是对这2个属性进行了设置和读取。
- 在te文件的目录下,存在一个property.te文件,在此处创建一个属性类型
+type vendor_tp_prop, property_type, vendor_property_type;
- 在property_contexts中,将具体的2个属性给到我们创建的vendor_tp_prop,参考如下
- 在我们自己的te文件中,赋予属性权限,语句如下:
set_prop(multi_tpinsmod, vendor_tp_prop)
- 实现后,再次编译,没问题,通过。
selinux注意事项
- 不要跨分区调用,如system调用vendor里的文件
- 部分无法执行的方法可以通过属性的方式让更高权限的init来执行:如在服务里设置某个属性,然后由init在init.rc中触发相应的动作,以此来绕过权限的限制