本頁面提供在 Android 中新增或定義系統屬性的標準方法,以及重構現有系統屬性的指南。除非有強烈的相容性問題,否則請務必在重構時遵循這些指南。
步驟 1:定義系統屬性
新增系統屬性時,請決定屬性的名稱,並將其與 SELinux 屬性內容建立關聯。如果沒有合適的現有內容,請建立新的內容。存取屬性時會使用名稱;屬性內容則用於控管 SELinux 的可存取性。名稱可以是任何字串,但 AOSP 建議採用結構化格式,讓名稱更清楚明瞭。
屬性名稱
請使用下列格式,並採用蛇形命名法:
[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]
針對 prefix 元素,使用「」(省略)、ro (僅設定一次的屬性) 或 persist (重新啟動後仍保留的屬性)。
注意事項
只有在確定日後不需要 prefix 可寫入時,才使用 ro。** 請勿指定 ro 前置字元。** 請改用 sepolicy 將 prefix 設為唯讀 (也就是只能由 init 寫入)。
只有在確定值必須在重新啟動後保留,且使用系統屬性是唯一選項時,才使用 persist。
Google 會嚴格審查具有 ro 或 persist 屬性的系統屬性。
group 用於匯總相關屬性。這項屬性是子系統名稱,用途類似於 audio 或 telephony。請勿使用含糊不清或過於籠統的字詞,例如 sys、system、dev、default 或 config。
一般做法是使用網域類型名稱,該網域類型具有系統屬性的獨占讀取或寫入存取權。舉例來說,對於 vold 程序具有寫入存取權的系統屬性,通常會使用 vold (程序的網域類型名稱) 做為群組名稱。
如需進一步分類屬性,請新增 subgroup,但請避免使用含糊不清或過於複雜的詞彙來描述這個元素。(你也可以有多個 subgroup。)
已定義許多群組名稱。檢查 system/sepolicy/private/property_contexts 檔案,盡可能使用現有的群組名稱,而不是建立新名稱。下表提供常用群組名稱的範例。
| 網域 | 群組 (和子群組) |
|---|---|
| 藍牙相關 | bluetooth |
| 核心指令列中的系統屬性 | boot |
| 用於識別建構版本的 sysprop | build
|
| 電話相關 | telephony |
| 音訊相關 | audio |
| 圖像相關 | graphics |
| vold 相關 | vold |
以下定義先前正規運算式範例中 name 和 type 的用法。
[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]
name可識別群組中的系統屬性。type是選用元素,可說明系統屬性的類型或意圖。舉例來說,與其將系統屬性命名為audio.awesome_feature_enabled或audio.awesome_feature,不如重新命名為audio.awesome_feature.enabled,以反映系統屬性類型和意圖。
類型沒有特定規則,以下是使用建議:
enabled:如果類型是布林值系統屬性,用來開啟或關閉某項功能,請使用這個值。config:如果意圖是澄清系統屬性「不」代表系統的動態狀態,而是代表預先設定的值 (例如唯讀項目),請使用這個屬性。List:如果這是系統屬性,且值為清單,請使用這個屬性。Timeoutmillis:如果這是以毫秒為單位的逾時值系統屬性,請使用這個值。
例如:
persist.radio.multisim.configdrm.service.enabled
房源情境
新的 SELinux 屬性內容架構可提供更精細的粒度和更具描述性的名稱。與屬性名稱使用的格式類似,AOSP 建議採用下列格式:
{group}[_{subgroup}]*_prop
這些字詞的定義如下:
group 和 subgroup 的意義與先前的範例規則運算式定義相同。舉例來說,vold_config_prop 代表來自供應商的設定,且應由 vendor_init 設定,而 vold_status_prop 或 vold_prop 則代表要公開 vold 目前狀態的屬性。
為屬性內容命名時,請選擇能反映屬性一般用途的名稱。請特別避免使用下列類型的字詞:
- 過於籠統或模稜兩可的字詞,例如
sys、system、default。 - 直接編碼無障礙功能的字詞,例如
exported、apponly、ro、public、private。
建議使用 vold_config_prop 這類名稱,而非 exported_vold_prop 或 vold_vendor_writable_prop。
類型
屬性類型可以是下表列出的其中一種。
| 類型 | 定義 |
|---|---|
| 布林值 | true 或 1 代表 true,false 或 0 代表 false |
| 整數 | 帶正負號的 64 位元整數 |
| 不帶正負號的整數 | 無正負號的 64 位元整數 |
| 雙項 | 雙精度浮點數 |
| 字串 | 任何有效的 UTF-8 字串 |
| enum | 值可以是任何有效的 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 外部讀取及寫入的屬性 |
盡可能縮小系統屬性的存取範圍。過去,廣泛存取權導致應用程式損壞和安全性漏洞。在劃定範圍時,請思考下列問題:
- 是否需要保留這個系統屬性?(如果是,原因為何?)
- 哪個程序應具備這項資源的讀取權?
- 哪些程序應具備這項資源的寫入權限?
請使用上述問題和下列決策樹狀圖,判斷適當的存取範圍。
圖 1. 決定系統屬性存取範圍的決策樹
步驟 3:新增至 system/sepolicy
存取 sysprop 時,SELinux 會控管程序的存取權。確定所需無障礙程度後,請在 system/sepolicy 下方定義屬性內容,並新增 allow 和 neverallow 規則,說明程序可讀取或寫入的內容 (以及不得讀取或寫入的內容)。
首先,請在 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)
其次,授予資源內容的讀取和 (或) 寫入權限。在 system/sepolicy/public/{domain}.te 或 system/sepolicy/private/{domain}.te 檔案中,使用 set_prop 和 get_prop 巨集授予存取權。請盡可能使用 private;只有在 set_prop 或 get_prop 巨集會影響核心網域以外的網域時,才適合使用 public。
例如,在 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.tesystem/sepolicy/private/coredomain.tesystem/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]
您可以視需要指定屬性類型,類型可以是下列其中之一:
boolintuintdoubleenum [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 OS 模組化,這一點尤其重要。有了 Treble,系統、供應商和產品分割區就能各自更新。透過 Mainline,作業系統的部分內容會模組化為可更新的模組 (以 APEX 或 APK 形式)。
如果系統屬性要用於可更新的軟體片段 (例如系統和供應商分割區),就必須穩定。不過,如果該物件只在特定 Mainline 模組中使用,您就可以變更其名稱、型別或屬性內容,甚至移除該物件。
請回答下列問題,判斷系統屬性的穩定性:
- 這個系統屬性是否要由合作夥伴設定 (或針對每個裝置設定不同值)?如有,必須穩定。
- 這個 AOSP 定義的系統屬性是否要寫入或讀取非系統分割區 (例如
vendor.img或product.img) 中的程式碼 (而非程序)?如有,必須穩定。 - 這個系統屬性是否會跨 Mainline 模組存取,或是跨 Mainline 模組和平台中無法更新的部分存取?如有,必須穩定。
針對穩定的系統屬性,請正式將每個屬性定義為 API,並使用 API 存取系統屬性,如步驟 6 所述。
步驟 5:在建構時設定屬性
使用 Makefile 變數在建構時設定屬性。從技術上來說,這些值會內建於 {partition}/build.prop。然後 init 會讀取 {partition}/build.prop 來設定屬性。這類變數有兩組:PRODUCT_{PARTITION}_PROPERTIES 和 TARGET_{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:在執行階段存取屬性
屬性可在執行階段讀取及寫入。
Init 指令碼
初始化指令碼檔案 (通常是 *.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 殼層指令
您可以使用 getprop 或 setprop 殼層指令,分別讀取或寫入屬性。詳情請叫用 getprop --help 或 setprop --help。
$ adb shell getprop ro.vndk.version
$
$ adb shell setprop security.perf_harden 0
將 Sysprop 做為 C++/Java/Rust 的 API
以 sysprop 做為 API,您可以定義系統屬性,並使用具體且經過型別化的自動產生 API。使用 Public 設定 scope,也能讓產生的 API 跨界供模組使用,並確保 API 穩定性。以下是 .sysprop 檔案、Android.bp 模組,以及使用這些模組的 C++、Java 和 Rust 程式碼範例。
# 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",
}
// Rust, Java and C++ modules can link against the sysprop_library
rust_binary {
rustlibs: ["libaudioprops_rust"],
…
}
java_library {
static_libs: ["AudioProps"],
…
}
cc_binary {
static_libs: ["libAudioProps"],
…
}
// Rust code accessing generated API.
// Get volume. Use 50 as the default value.
let vol = audioprops::volume_level()?.unwrap_or_else(50);
// 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 和 Rust 低階屬性函式和方法
即使您可以使用低階 C/C++ 或 Rust 函式,或是低階 Java 方法,也請盡可能將 Sysprop 做為 API。
libc、libbase 和 libcutils 提供 C++ 系統屬性函式。libc 是基礎 API,而 libbase 和 libcutils 函式是包裝函式。如果可以,請使用 libbase sysprop 函式,因為這是最方便的方法,主機二進位檔也可以使用 libbase 函式。詳情請參閱 sys/system_properties.h (libc)、android-base/properties.h (libbase) 和 cutils/properties.h (libcutils)。
android.os.SystemProperties 類別提供 Java 系統屬性方法。
rustutils::system_properties 模組提供 Rust 系統屬性函式和型別。
附錄:新增供應商專屬屬性
合作夥伴 (包括參與 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_redpersist.vendor.faceauth.use_disk_cachero.odm.hardware.platform
所有供應商屬性內容都必須以 vendor_ 開頭。這也是為了相容性。例如:
vendor_radio_prop。vendor_faceauth_prop。vendor_usb_prop。
供應商有責任命名及維護屬性,因此除了供應商命名空間規定外,請按照步驟 2 建議的格式操作。
廠商專屬 SEPolicy 規則和 property_contexts
供應商屬性可由 vendor_internal_prop 巨集定義。將您在 BOARD_VENDOR_SEPOLICY_DIRS 目錄中定義的供應商專屬規則放入其中。舉例來說,假設您要在 Coral 中定義供應商 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
供應商屬性的限制
由於系統和產品分割區無法依附於供應商,因此請勿允許從 system、system-ext 或 product 分割區存取供應商屬性。
附錄:重新命名現有屬性
如果必須淘汰屬性並改用新屬性,請使用 Sysprop 做為 API,重新命名現有屬性。這樣做可同時指定舊版名稱和新屬性名稱,維持回溯相容性。具體來說,您可以在 .sysprop 檔案中,透過 legacy_prop_name 欄位設定舊版名稱。產生的 API 會嘗試讀取 prop_name,如果 prop_name 不存在,則會使用 legacy_prop_name。
舉例來說,下列步驟會將 awesome_feature_foo_enabled 重新命名為 foo.awesome_feature.enabled。
在 foo.sysprop 檔案中
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 可寫入,就無法重新命名。