1 // Copyright 2022, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Retrieve and populate information about userspace.
16
17 use kmr_wire::SetHalInfoRequest;
18 use regex::Regex;
19
20 // The OS version property is of form "12" or "12.1" or "12.1.3".
21 const OS_VERSION_PROPERTY: &str = "ro.build.version.release";
22 const OS_VERSION_REGEX: &str = r"^(?P<major>\d{1,2})(\.(?P<minor>\d{1,2}))?(\.(?P<sub>\d{1,2}))?$";
23
24 // The patchlevel properties are of form "YYYY-MM-DD".
25 /// Name of property that holds the OS patchlevel.
26 pub const OS_PATCHLEVEL_PROPERTY: &str = "ro.build.version.security_patch";
27 const VENDOR_PATCHLEVEL_PROPERTY: &str = "ro.vendor.build.security_patch";
28 const PATCHLEVEL_REGEX: &str = r"^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})$";
29
30 // Just use [`String`] for errors here.
31 type Error = String;
32
33 /// Retrieve a numeric value from a possible match.
extract_u32(value: Option<regex::Match>) -> Result<u32, Error>34 fn extract_u32(value: Option<regex::Match>) -> Result<u32, Error> {
35 match value {
36 Some(m) => {
37 let s = m.as_str();
38 match s.parse::<u32>() {
39 Ok(v) => Ok(v),
40 Err(e) => Err(format!("failed to parse integer: {:?}", e)),
41 }
42 }
43 None => Err("failed to find match".to_string()),
44 }
45 }
46
47 /// Retrieve the value of a property identified by `name`.
get_property(name: &str) -> Result<String, Error>48 pub fn get_property(name: &str) -> Result<String, Error> {
49 match rustutils::system_properties::read(name) {
50 Ok(Some(value)) => Ok(value),
51 Ok(None) => Err(format!("no value for property {}", name)),
52 Err(e) => Err(format!("failed to get property {}: {:?}", name, e)),
53 }
54 }
55
56 /// Extract a patchlevel in form YYYYMM from a "YYYY-MM-DD" property value.
extract_truncated_patchlevel(prop_value: &str) -> Result<u32, Error>57 pub fn extract_truncated_patchlevel(prop_value: &str) -> Result<u32, Error> {
58 let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX)
59 .map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?;
60
61 let captures = patchlevel_regex
62 .captures(prop_value)
63 .ok_or_else(|| "failed to match patchlevel regex".to_string())?;
64 let year = extract_u32(captures.name("year"))?;
65 let month = extract_u32(captures.name("month"))?;
66 if !(1..=12).contains(&month) {
67 return Err(format!("month out of range: {}", month));
68 }
69 // no day
70 Ok(year * 100 + month)
71 }
72
73 /// Extract a patchlevel in form YYYYMMDD from a "YYYY-MM-DD" property value.
extract_patchlevel(prop_value: &str) -> Result<u32, Error>74 pub fn extract_patchlevel(prop_value: &str) -> Result<u32, Error> {
75 let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX)
76 .map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?;
77
78 let captures = patchlevel_regex
79 .captures(prop_value)
80 .ok_or_else(|| "failed to match patchlevel regex".to_string())?;
81 let year = extract_u32(captures.name("year"))?;
82 let month = extract_u32(captures.name("month"))?;
83 if !(1..=12).contains(&month) {
84 return Err(format!("month out of range: {}", month));
85 }
86 let day = extract_u32(captures.name("day"))?;
87 if !(1..=31).contains(&day) {
88 return Err(format!("day out of range: {}", day));
89 }
90 Ok(year * 10000 + month * 100 + day)
91 }
92
93 /// Generate HAL information from property values.
populate_hal_info_from( os_version_prop: &str, os_patchlevel_prop: &str, vendor_patchlevel_prop: &str, ) -> Result<SetHalInfoRequest, Error>94 fn populate_hal_info_from(
95 os_version_prop: &str,
96 os_patchlevel_prop: &str,
97 vendor_patchlevel_prop: &str,
98 ) -> Result<SetHalInfoRequest, Error> {
99 let os_version_regex = Regex::new(OS_VERSION_REGEX)
100 .map_err(|e| format!("failed to compile version regexp: {:?}", e))?;
101 let captures = os_version_regex
102 .captures(os_version_prop)
103 .ok_or_else(|| "failed to match OS version regex".to_string())?;
104 let major = extract_u32(captures.name("major"))?;
105 let minor = extract_u32(captures.name("minor")).unwrap_or(0u32);
106 let sub = extract_u32(captures.name("sub")).unwrap_or(0u32);
107 let os_version = (major * 10000) + (minor * 100) + sub;
108
109 Ok(SetHalInfoRequest {
110 os_version,
111 os_patchlevel: extract_truncated_patchlevel(os_patchlevel_prop)?,
112 vendor_patchlevel: extract_patchlevel(vendor_patchlevel_prop)?,
113 })
114 }
115
116 /// Populate a [`SetHalInfoRequest`] based on property values read from the environment.
populate_hal_info() -> Result<SetHalInfoRequest, Error>117 pub fn populate_hal_info() -> Result<SetHalInfoRequest, Error> {
118 let os_version_prop = get_property(OS_VERSION_PROPERTY)
119 .map_err(|e| format!("failed to retrieve property: {:?}", e))?;
120 let os_patchlevel_prop = get_property(OS_PATCHLEVEL_PROPERTY)
121 .map_err(|e| format!("failed to retrieve property: {:?}", e))?;
122 let vendor_patchlevel_prop = get_property(VENDOR_PATCHLEVEL_PROPERTY)
123 .map_err(|e| format!("failed to retrieve property: {:?}", e))?;
124
125 populate_hal_info_from(&os_version_prop, &os_patchlevel_prop, &vendor_patchlevel_prop)
126 }
127
128 #[cfg(test)]
129 mod tests {
130 use super::*;
131 use kmr_wire::SetHalInfoRequest;
132 #[test]
test_hal_info()133 fn test_hal_info() {
134 let tests = vec![
135 (
136 "12",
137 "2021-02-02",
138 "2022-03-04",
139 SetHalInfoRequest {
140 os_version: 120000,
141 os_patchlevel: 202102,
142 vendor_patchlevel: 20220304,
143 },
144 ),
145 (
146 "12.5",
147 "2021-02-02",
148 "2022-03-04",
149 SetHalInfoRequest {
150 os_version: 120500,
151 os_patchlevel: 202102,
152 vendor_patchlevel: 20220304,
153 },
154 ),
155 (
156 "12.5.7",
157 "2021-02-02",
158 "2022-03-04",
159 SetHalInfoRequest {
160 os_version: 120507,
161 os_patchlevel: 202102,
162 vendor_patchlevel: 20220304,
163 },
164 ),
165 ];
166 for (os_version, os_patch, vendor_patch, want) in tests {
167 let got = populate_hal_info_from(os_version, os_patch, vendor_patch).unwrap();
168 assert_eq!(
169 got, want,
170 "Mismatch for input ({}, {}, {})",
171 os_version, os_patch, vendor_patch
172 );
173 }
174 }
175
176 #[test]
test_invalid_hal_info()177 fn test_invalid_hal_info() {
178 let tests = vec![
179 ("xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
180 ("12.xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
181 ("12.5.xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
182 ("12", "20212-02-02", "2022-03-04", "failed to match patchlevel regex"),
183 ("12", "2021-xx-02", "2022-03-04", "failed to match patchlevel"),
184 ("12", "2021-13-02", "2022-03-04", "month out of range"),
185 ("12", "2022-03-04", "2021-xx-02", "failed to match patchlevel"),
186 ("12", "2022-03-04", "2021-13-02", "month out of range"),
187 ("12", "2022-03-04", "2021-03-32", "day out of range"),
188 ];
189 for (os_version, os_patch, vendor_patch, want_err) in tests {
190 let result = populate_hal_info_from(os_version, os_patch, vendor_patch);
191 assert!(result.is_err());
192 let err = result.unwrap_err();
193 assert!(
194 err.contains(want_err),
195 "Mismatch for input ({}, {}, {}), got error '{}', want '{}'",
196 os_version,
197 os_patch,
198 vendor_patch,
199 err,
200 want_err
201 );
202 }
203 }
204 }
205