添加系统属性

此页面提供了在 Android 中添加或定义系统属性的规范方法,以及重构现有系统属性的指南。确保您在重构时使用这些指南,除非您有一个强烈的兼容性问题,否则会另行规定。

步骤 1:定义系统属性

添加系统属性时,请确定属性的名称,并将其与 SELinux 属性上下文相关联。如果没有合适的现有上下文,请创建一个新上下文。访问属性时使用该名称;属性上下文用于控制 SELinux 的可访问性。名称可以是任何字符串,但 AOSP 建议您遵循结构化格式以使其清晰。

属性名称

将此格式与 snake_case 大小写一起使用:

[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]

对元素prefix使用“”(省略)、 ro (用于仅设置一次的属性)或persist (用于在重新启动后持续存在的属性)。

注意事项

仅当您确定将来不需要prefix可写时才使用ro 。 ** 不要指定ro前缀。** 相反,依靠 sepolicy 使prefix只读(换句话说,只能由init写入)。

仅当您确定必须在重新启动后保留该值并且使用系统属性是您唯一的选择时才使用persist 。 (有关详细信息,请参阅准备部分。)

Google 严格审查具有ropersist属性的系统属性。

术语group用于聚合相关属性。它的目的是作为一个子系统名称,在使用中类似于audiotelephony不要使用模棱两可或重载的术语,例如syssystemdevdefaultconfig

通常的做法是使用对系统属性具有独占读取或写入访问权限的进程的域类型名称。例如,对于vold进程具有写入权限的系统属性,通常使用vold (进程的域类型名称)作为组名。

如果需要,添加subgroup以进一步对属性进行分类,但避免使用模棱两可或重载的术语来描述此元素。 (您也可以有多subgroup 。)

许多组名已被定义。检查system/sepolicy/private/property_contexts文件并尽可能使用现有组名,而不是创建新组名。下表提供了常用组名的示例。

领域组(和子组)
蓝牙相关bluetooth
来自内核命令行的 sysprops boot
标识构建的 sysprops build
电话相关telephony
音频相关audio
图形相关graphics
卷相关vold

下面定义了前面正则表达式示例nametype的使用。

[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]

  • name标识组内的系统属性。

  • type是一个可选元素,用于阐明系统属性的类型或意图。例如,不要将 sysprop 命名为audio.awesome_feature_enabled或仅命名为audio.awesome_feature ,而是将其重命名为audio.awesome_feature.enabled以反映系统属性类型和意图。

关于类型必须是什么没有具体规则。这些是使用建议:

  • enabled :如果类型是用于打开或关闭功能的布尔系统属性,则使用。
  • config :如果目的是澄清系统属性不代表系统的动态状态,则使用;它代表一个预配置的值(例如,一个只读的东西)。
  • List :如果它是一个值为列表的系统属性,则使用它。
  • Timeoutmillis :如果它是超时值的系统属性,则以毫秒为单位使用。

例子:

  • persist.radio.multisim.config
  • drm.service.enabled

属性上下文

新的 SELinux 属性上下文方案允许更精细的粒度和更具描述性的名称。与用于属性名称的内容类似,AOSP 建议使用以下格式:

{group}[_{subgroup}]*_prop

术语定义如下:

groupsubgroup具有与前面示例 regex定义的含义相同的含义。例如, vold_config_prop表示来自供应商的配置并由vendor_init设置的属性,而vold_status_prop或只是vold_prop表示将公开vold的当前状态的属性。

命名属性上下文时,选择反映属性一般用途的名称。尤其要避免以下类型的术语:

  • 看起来过于笼统和模棱两可的术语,例如syssystemdefault
  • 直接编码可访问性的术语:例如exportedapponlyropublicprivate

喜欢使用诸如vold_config_prop exported_vold_prop vold_vendor_writable_prop等。

类型

属性类型可以是表中列出的以下之一。

类型定义
布尔值true1表示 true, false0表示 false
整数有符号 64 位整数
无符号整数无符号 64 位整数
双倍的双精度浮点
细绳任何有效的 UTF-8 字符串
枚举值可以是任何没有空格的有效 UTF-8 字符串
以上列表逗号 ( , ) 用作分隔符
整数列表[1, 2, 3]存储为1,2,3

在内部,所有属性都存储为字符串。您可以通过将类型指定为property_contexts文件来强制执行该类型。有关更多信息,请参阅步骤 3中的property_contexts

第 2 步:确定所需的可访问性级别

有四个定义属性的辅助宏。

辅助功能类型意义
system_internal_prop仅在/system中使用的属性
system_restricted_prop/system外部读取但未写入的属性
system_vendor_config_prop/system外部读取并且仅由vendor_init写入的属性
system_public_prop/system外部读写的属性

尽可能缩小对系统属性的访问范围。过去,广泛的访问会导致应用程序损坏和安全漏洞。在确定范围时考虑以下问题:

  • 这个系统属性是否需要持久化? (如果是,为什么?)
  • 哪个进程应该对该属性具有读取权限?
  • 哪个进程应该对此属性有写访问权?

使用前面的问题和下面的决策树作为确定适当访问范围的工具。

Decision tree for determining the scope of access

图 1.确定系统属性访问范围的决策树

第 3 步:将其添加到系统/sepolicy

访问 sysprop 时,SELinux 控制进程的可访问性。在确定需要什么级别的可访问性之后,在system/sepolicy下定义属性上下文,以及关于允许(和不允许)哪些进程读取或写入的附加允许禁止规则。

首先,在system/sepolicy/public/property.te文件中定义属性上下文。如果属性是系统内部的,请在system/sepolicy/private/property.te文件中定义它。使用提供系统属性所需的可访问性的system_[accessibility]_prop([context])宏之一。这是system/sepolicy/public/property.te文件的示例:

system_public_prop(audio_foo_prop)
system_vendor_config_prop(audio_bar_prop)

system/sepolicy/private/property.te文件中添加的示例:

system_internal_prop(audio_baz_prop)

其次,授予对属性上下文的读取和(或)写入访问权限。使用set_propget_prop宏在system/sepolicy/public/{domain}.tesystem/sepolicy/private/{domain}.te文件中授予访问权限。尽可能使用privatepublic仅当set_propget_prop宏影响核心域之外的任何域时才适用。

例如,在system/sepolicy/private/audio.te文件中:

set_prop(audio, audio_foo_prop)
set_prop(audio, audio_bar_prop)

例如,在system/sepolicy/public/domain.te文件中:

get_prop(domain, audio_bar_prop)

第三,添加一些 neverallow 规则以进一步减少宏范围内的可访问性。例如,假设您使用了system_restricted_prop ,因为您的系统属性必须由供应商进程读取。如果不是所有供应商进程都需要读取访问权限,并且仅特定一组进程(例如vendor_init )需要,则禁止不需要读取访问权限的供应商进程。

使用以下语法来限制写入和读取访问:

限制写访问:

neverallow [domain] [context]:property_service set;

要限制读取访问:

neverallow [domain] [context]:file no_rw_file_perms;

如果 neverallow 规则绑定到特定域,则将 neverallow 规则放在system/sepolicy/private/{domain}.te文件中。对于更广泛的 neverallow 规则,请在适当的地方使用诸如这些的通用域:

  • system/sepolicy/private/property.te
  • system/sepolicy/private/coredomain.te
  • system/sepolicy/private/domain.te

system/sepolicy/private/audio.te文件中,放置以下内容:

neverallow {
    domain -init -audio
} {audio_foo_prop audio_bar_prop}:property_service set;

system/sepolicy/private/property.te文件中,放置以下内容:

neverallow {
    domain -coredomain -vendor_init
} audio_prop:file no_rw_file_perms;

请注意, {domain -coredomain}捕获所有供应商进程。所以{domain -coredomain -vendor_init}表示“vendor_init之外的所有供应商进程”。

最后,将系统属性与属性上下文相关联。这可确保将授予的访问权限和应用于属性上下文的 neverallow 规则应用于实际属性。为此,向property_contexts文件添加一个条目,该文件描述系统属性和属性上下文之间的映射。在此文件中,您可以指定单个属性或要映射到上下文的属性的前缀。

这是映射单个属性的语法:

[property_name] u:object_r:[context_name]:s0 exact [type]

这是映射前缀的语法:

[property_name_prefix] u:object_r:[context_name]:s0 prefix [type]

您可以选择指定属性的类型,可以是以下之一:

  • bool
  • int
  • uint
  • double
  • enum [list of possible values...]
  • string (将string用于列表属性。)

尽可能确保每个条目都有其指定的类型,因为在设置property时强制执行type 。以下示例显示了如何编写映射:

# binds a boolean property "ro.audio.status.enabled"
# to the context "audio_foo_prop"
ro.audio.status.enabled u:object_r:audio_foo_prop:s0 exact bool

# binds a boolean property "vold.decrypt.status"
# to the context "vold_foo_prop"
# The property can only be set to one of these: on, off, unknown
vold.decrypt.status u:object_r:vold_foo_prop:s0 exact enum on off unknown

# binds any properties starting with "ro.audio.status."
# to the context "audio_bar_prop", such as
# "ro.audio.status.foo", or "ro.audio.status.bar.baz", and so on.
ro.audio.status. u:object_r:audio_bar_prop:s0 prefix

当精确条目和前缀条目冲突时,精确条目优先。有关更多示例,请参阅system/sepolicy/private/property_contexts

第 4 步:确定稳定性要求

稳定性是系统属性的另一个方面,它不同于可访问性。稳定性是关于将来是否可以更改系统属性(例如重命名,甚至删除)。随着 Android 操作系统变得模块化,这一点尤其重要。使用 Treble,系统、供应商和产品分区可以相互独立地更新。使用 Mainline,操作系统的某些部分被模块化为可更新模块(在 APEX 或 APK 中)。

如果系统属性用于跨可更新的软件片段,例如跨系统和供应商分区,它必须是稳定的。但是,如果它仅在特定 Mainline 模块中使用,您可以更改其名称、类型或属性上下文,甚至将其删除。

询问以下问题以确定系统属性的稳定性:

  • 此系统属性是否打算由合作伙伴配置(或每个设备的配置不同)?如果是,它必须是稳定的。
  • 此 AOSP 定义的系统属性是否旨在写入或读取存在于vendor.imgproduct.img等非系统分区中的代码(而非进程)?如果是,它必须是稳定的。
  • 此系统属性是跨 Mainline 模块还是跨 Mainline 模块和平台的不可更新部分访问?如果是,它必须是稳定的。

对于稳定的系统属性,将每个属性正式定义为 API,并使用 API 访问系统属性,如步骤 6中所述。

第 5 步:在构建时设置属性

在构建时使用 makefile 变量设置属性。从技术上讲,这些值被烘焙到{partition}/build.prop 。然后init读取{partition}/build.prop来设置属性。有两组这样的变量: PRODUCT_{PARTITION}_PROPERTIESTARGET_{PARTITION}_PROP

PRODUCT_{PARTITION}_PROPERTIES包含属性值列表。语法是{prop}={value}{prop}?={value}

{prop}={value}是确保{prop}设置为{value}的正常分配;每个属性只能有一个这样的分配。

{prop}?={value}是一个可选的赋值;仅当没有任何{prop}={value}分配时,{ {prop}才设置为{value} }。如果存在多个可选分配,则第一个获胜。

# sets persist.traced.enable to 1 with system/build.prop
PRODUCT_SYSTEM_PROPERTIES += persist.traced.enable=1

# sets ro.zygote to zygote32 with system/build.prop
# but only when there are no other assignments to ro.zygote
# optional are useful when giving a default value to a property
PRODUCT_SYSTEM_PROPERTIES += ro.zygote?=zygote32

# sets ro.config.low_ram to true with vendor/build.prop
PRODUCT_VENDOR_PROPERTIES += ro.config.low_ram=true

TARGET_{PARTITION}_PROP包含文件列表,直接发送到{partition}/build.prop 。每个文件都包含一个{prop}={value}对列表。

# example.prop

ro.cp_system_other_odex=0
ro.adb.secure=0
ro.control_privapp_permissions=disable

# emits example.prop to system/build.prop
TARGET_SYSTEM_PROP += example.prop

有关更多详细信息,请参阅build/make/core/sysprop.mk

第 6 步:在运行时访问属性

当然,可以在运行时读取和写入属性。

初始化脚本

初始化脚本文件(通常是 *.rc 文件)可以通过${prop}${prop:-default}读取属性,可以设置在属性变为特定值时运行的操作,并且可以使用setprop写入属性命令。

# when persist.device_config.global_settings.sys_traced becomes 1,
# set persist.traced.enable to 1
on property:persist.device_config.global_settings.sys_traced=1
    setprop persist.traced.enable 1

# when security.perf_harden becomes 0,
# write /proc/sys/kernel/sample_rate to the value of
# debug.sample_rate. If it's empty, write -100000 instead
on property:security.perf_harden=0
    write /proc/sys/kernel/sample_rate ${debug.sample_rate:-100000}

getprop 和 setprop shell 命令

您可以分别使用getpropsetprop shell 命令来读取或写入属性。有关更多详细信息,请调用getprop --helpsetprop --help

$ adb shell getprop ro.vndk.version
$
$ adb shell setprop security.perf_harden 0

Sysprop 作为 C++/Java 的 API

使用 sysprop 作为 API,您可以定义系统属性并使用自动生成的具体和类型化的 API。使用Public设置scope还可以使生成的 API 跨边界供模块使用,并确保 API 稳定性。这是.sysprop文件、 Android.bp模块以及使用它们的 C++ 和 Java 代码的示例。

# AudioProps.sysprop
# module becomes static class (Java) / namespace (C++) for serving API
module: "android.sysprop.AudioProps"
# owner can be Platform or Vendor or Odm
owner: Platform
# one prop defines one property
prop {
    prop_name: "ro.audio.volume.level"
    type: Integer
    scope: Public
    access: ReadWrite
    api_name: "volume_level"
}
…

// Android.bp
sysprop_library {
    name: "AudioProps",
    srcs: ["android/sysprop/AudioProps.sysprop"],
    property_owner: "Platform",
}

// Both java and cc module can link against sysprop_library
java_library {
    static_libs: ["AudioProps"],
    …
}

cc_binary {
    static_libs: ["AudioProps"],
    …
}

// Java codes accessing generated API
// get volume. use 50 as the default value.
int vol = android.sysprop.AudioProps.volume_level().orElse(50);
// add 10 to the volume level.
android.sysprop.AudioProps.volume_level(vol + 10);

// C++ codes accessing generated API
// get volume. use 50 as the default value.
int vol = android::sysprop::AudioProps::volume_level().value_or(50);
// add 10 to the volume level.
android::sysprop::AudioProps::volume_level(vol + 10);

有关更多信息,请参阅将系统属性实现为 API

C/C++ 和 Java 低级属性函数和方法

使用 Sysprop 作为 API,即使您可以使用低级 C/C++ 函数或低级 Java 方法。尽可能优先使用 Sysprop。

libclibbaselibcutils提供 C++ 系统属性函数。 libc具有底层 API,而libbaselibcutils函数是包装器。如果可能,请使用libbase sysprop 函数;它们是最方便的,宿主二进制文件可以使用libbase函数。有关更多详细信息,请参阅sys/system_properties.h ( libc )、 android-base/properties.h ( libbase ) 和cutils/properties.h ( libcutils )。

android.os.SystemProperties类提供 Java 系统属性方法。

附录:添加特定于供应商的属性

合作伙伴(包括从事 Pixel 开发工作的 Google 员工)希望定义特定于硬件(或特定于设备)的系统属性。供应商特定属性是合作伙伴拥有的属性,它们是他们自己的硬件或设备独有的,而不是平台独有的。由于这些依赖于硬件或设备,因此它们应在/vendor/odm分区中使用。

自 Project Treble 以来,平台属性和供应商属性已完全分离,以防止它们发生冲突。下面介绍如何定义供应商属性,并说明必须始终使用哪些供应商属性。

属性和上下文名称的命名空间

所有供应商属性必须以以下前缀之一开头,以防止它们与其他分区的属性发生冲突。

  • ctl.odm.
  • ctl.vendor.
  • ctl.start$odm.
  • ctl.start$vendor.
  • ctl.stop$odm.
  • ctl.stop$vendor.
  • init.svc.odm.
  • init.svc.vendor.
  • ro.odm.
  • ro.vendor.
  • odm.
  • persist.odm.
  • persist.vendor.
  • vendor.

注意ro.hardware.允许作为前缀,但仅用于兼容性。不要将其用于普通属性。

以下示例都使用前面列出的前缀之一:

  • vendor.display.primary_red
  • persist.vendor.faceauth.use_disk_cache
  • ro.odm.hardware.platform

所有供应商属性上下文必须以vendor_ 。这也是为了兼容性。以下是示例:

  • vendor_radio_prop
  • vendor_faceauth_prop
  • vendor_usb_prop

命名和维护属性是供应商的责任,因此除了供应商命名空间要求外,还应遵循步骤 2中建议的格式。

供应商特定的 SEPolicy 规则和 property_contexts

与平台属性类似,供应商属性可以由以下宏之一定义。

辅助功能类型意义
vendor_internal_prop仅在/vendor中使用的属性
vendor_restricted_prop/vendor外部读取但未写入的属性
vendor_public_prop/vendor外部读写的属性

将您定义的供应商特定规则放在BOARD_VENDOR_SEPOLICY_DIRS目录中。例如,假设您正在珊瑚中定义供应商 faceauth 属性。

BoardConfig.mk文件(或任何BoardConfig.mk包含)中,输入以下内容:

BOARD_VENDOR_SEPOLICY_DIRS := device/google/coral-sepolicy

device/google/coral-sepolicy/private/property.te文件中,输入以下内容:

vendor_internal_prop(vendor_faceauth_prop)

device/google/coral-sepolicy/private/property_contexts文件中,输入以下内容:

vendor.faceauth.trace u:object_r:vendor_faceauth_prop:s0 exact bool

供应商属性的限制

因为系统和产品分区不能依赖于供应商,所以绝不允许从systemsystem-extproduct分区访问供应商属性。

附录:重命名现有属性

当您必须弃用某个属性并迁移到新属性时,请使用Sysprop 作为 API来重命名现有属性。这通过同时指定旧名称和新属性名称来保持向后兼容性。具体来说,您可以通过.sysprop文件中的legacy_prop_name字段设置旧名称。生成的 API 尝试读取prop_name ,如果prop_name不存在,则使用legacy_prop_name

例如,以下步骤将awesome_feature_foo_enabled重命名为foo.awesome_feature.enabled

foo.sysprop文件中(在 Java 中)

module: "android.sysprop.foo"
owner: Platform
prop {
    api_name: "is_awesome_feature_enabled"
    type: Boolean
    scope: Public
    access: Readonly
    prop_name: "foo.awesome_feature.enabled"
    legacy_prop_name: "awesome_feature_foo_enabled"
}

在 C++ 代码中

// is_awesome_feature_enabled() reads "foo.awesome_feature.enabled".
// If it doesn't exist, reads "awesome_feature_foo_enabled" instead
using android::sysprop::foo;

bool enabled = foo::is_awesome_feature_enabled().value_or(false);

请注意以下警告:

  • 首先,您不能更改 sysprop 的类型。例如,您不能将int道具转换为string道具。您只能更改名称。

  • 其次,只有读取 API 回退到旧名称。写入 API 不会回退。如果 sysprop 是可写的,则不能重命名它。