新增系統屬性

本頁提供在 Android 中新增或定義系統屬性的標準方法,並提供重構現有系統屬性的指南。除非您有明顯的相容性問題,否則您在重構時請務必使用本指南。

步驟 1:定義系統屬性

新增系統屬性時,決定屬性的名稱,並將該名稱與 SELinux 屬性結構定義建立關聯。如果沒有合適的現有背景資訊,請建立新的背景資訊。名稱用於存取屬性;屬性內容可用來控制 SELinux 中的無障礙功能。名稱可以是任何字串,但 Android 開放原始碼計畫建議您採用結構化格式,以便清楚辨識。

屬性名稱

請使用 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
來自核心 cmdline 的 sysprops boot
用於識別建構作業的 sysprops build
電話通訊 telephony
音訊相關 audio
圖像相關 graphics
伏特相關 vold

下列內容定義了在先前的規則運算式範例中如何使用 nametype

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

  • name 可識別群組中的系統屬性。

  • type 是選用元素,用於釐清系統屬性的類型或意圖。例如,將 sysprop 命名為 audio.awesome_feature_enabled,或只重新命名為 audio.awesome_feature,而是將 sysprop 重新命名為 audio.awesome_feature.enabled,以反映系統屬性類型和意圖。

系統並沒有關於類型之規定的特定規則;以下是使用建議:

  • enabled:如果類型是布林值系統屬性,可用於開啟或關閉功能,請使用該類型。
  • config:用途是指出系統屬性「不」代表系統的動態狀態,而代表的是預先設定的值 (例如唯讀項目)。
  • List:如果是系統屬性,且其值是清單,請使用這個欄位。
  • Timeoutmillis:如果是系統屬性的逾時值 (以毫秒為單位),請使用這個選項。

例如:

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

資源情境

新的 SELinux 屬性結構定義架構可讓您更精細、更明確的名稱。與屬性名稱的用途類似,Android 開放原始碼計畫建議使用以下格式:

{group}[_{subgroup}]*_prop

這些字詞的定義如下:

groupsubgroup 的意義與先前的規則運算式範例相同。舉例來說,vold_config_prop 表示是由供應商設定且應由 vendor_init 設定的屬性,而 vold_status_propvold_prop 則代表要公開 vold 目前狀態的屬性。

為屬性結構定義命名時,請選擇能反映屬性一般用途的名稱。請特別避免使用以下字詞類型:

  • 太過籠統且模稜兩可的字詞,例如 syssystemdefault
  • 直接為無障礙功能編碼的字詞,例如 exportedapponlyropublicprivate

建議名稱用途,例如 vold_config_propexported_vold_propvold_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 之外讀取及寫入的屬性

請盡可能縮小系統資源的存取權範圍。過去,廣泛存取權會導致應用程式故障和安全漏洞。設定範圍時,請考慮以下問題:

  • 需要保留這項系統屬性嗎?(如果有,原因為何?)
  • 哪個程序應具備這項資源的讀取權限?
  • 哪個程序應具備這項資源的寫入權限?

請使用前面的問題和下列決策樹狀圖,做為決定適當的存取範圍的工具。

用來判斷存取權範圍的決策樹

圖 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)

第二,授予屬性結構定義的讀取和 (或) 寫入權限。在 system/sepolicy/public/{domain}.tesystem/sepolicy/private/{domain}.te 檔案中,使用 set_propget_prop 巨集授予存取權。盡可能使用 private;只有在 set_propget_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)

第三,新增一些永不允許規則,進一步減少巨集限定範圍的無障礙功能。舉例來說,假設您已使用 system_restricted_prop,因為廠商程序必須讀取您的系統屬性。如果所有供應商程序不需要讀取權限,且只需要一組特定程序 (例如 vendor_init),請禁止不需要讀取權限的供應商程序。

請使用下列語法限制寫入及讀取權限:

如何限制寫入權限:

neverallow [domain] [context]:property_service set;

如何限制讀取權限:

neverallow [domain] [context]:file no_rw_file_perms;

如果永不允許規則繫結至特定網域,請在 system/sepolicy/private/{domain}.te 檔案中放置永不允許規則。針對範圍較廣的一律禁止規則,請視情況使用一般網域,例如:

  • 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 以外的所有供應商處理作業」

最後,將系統屬性與屬性結構定義建立關聯。這可以確保已授予的存取權,以及套用至屬性結構定義的永不允許規則,都能套用至實際屬性。方法是在 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 OS 採用模組化,這特別重要。使用 Treble 時,系統、供應商和產品分區可以獨立更新。透過 Mainline,作業系統的某些部分會模組化為可更新的模組 (在 APEX 或 APK 中)。

如果系統屬性適用於可更新的軟體元件 (例如跨系統和供應商分區),就必須保持穩定。不過,如果只在特定 Mainline 模組中使用,則可變更其名稱、類型或屬性結構定義,甚至將其移除。

請詢問下列問題,判斷系統屬性的穩定性:

  • 這個系統屬性是否應由合作夥伴設定 (或依個別裝置設定不同的設定)?如果是,就必須保持穩定。
  • 這個 Android 開放原始碼計畫定義的系統屬性是否打算寫入或讀取位於 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} 只有在沒有任何 {prop}={value} 指派時,才會設為 {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 指令碼

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 殼層指令

您可以分別使用 getpropsetprop 殼層指令來讀取或寫入屬性。詳情請參閱叫用 getprop --helpsetprop --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 低階屬性函式和方法

請盡可能使用 Sysprop 做為 API,即使您可以使用低階 C/C++ 函式或低階 Java 方法亦然。

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 系統屬性方法。

rustutils::system_properties 模組提供 Rust 系統屬性函式和類型。

附錄:新增供應商專屬資源

合作夥伴 (包括針對 Pixel 開發工作的 Google 員工) 想定義硬體專屬 (或裝置專屬) 的系統屬性。供應商專屬屬性為自家硬體或裝置 (而非平台) 專屬的合作夥伴擁有的屬性。由於這些是硬體或裝置而異,因此適合在 /vendor/odm 分區中使用。

自 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 巨集可以定義供應商屬性。請將您定義的供應商專屬規則放入 BOARD_VENDOR_SEPOLICY_DIRS 目錄。舉例來說,假設您在珊瑚紅中定義供應商臉部驗證屬性。

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 檔案中

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 會改回使用舊版名稱。Write API 不會復原。如果 sysprop 是可寫入的,您無法重新命名。