]>
Commit | Line | Data |
---|---|---|
1 | #include <darwintest.h> | |
2 | #include <darwintest_utils.h> | |
3 | #include <dispatch/dispatch.h> | |
4 | #include <fcntl.h> | |
5 | #include <stdio.h> | |
6 | #include <TargetConditionals.h> | |
7 | #include <unistd.h> | |
8 | #include <xpc/private.h> | |
9 | ||
10 | #define SYSTEM_VERSION_COMPAT_PLIST_PATH "/System/Library/CoreServices/SystemVersionCompat.plist" | |
11 | #define IOS_SYSTEM_VERSION_PLIST_PATH "/System/Library/CoreServices/iOSSystemVersion.plist" | |
12 | ||
13 | #define SYSTEM_VERSION_PLIST_FILENAME "SystemVersion.plist" | |
14 | #define SYSTEM_VERSION_PLIST_PATH ("/System/Library/CoreServices/" SYSTEM_VERSION_PLIST_FILENAME) | |
15 | ||
16 | #define PRODUCT_VERSION_KEY "ProductVersion" | |
17 | #define IOS_SUPPORT_VERSION_KEY "iOSSupportVersion" | |
18 | ||
19 | #define PRODUCT_VERSION_SYSCTL "kern.osproductversion" | |
20 | #define PRODUCT_VERSION_COMPAT_SYSCTL "kern.osproductversioncompat" | |
21 | ||
22 | T_GLOBAL_META(T_META_CHECK_LEAKS(false)); | |
23 | ||
24 | #if TARGET_OS_OSX | |
25 | static void | |
26 | check_system_version_compat_plist_exists(void) | |
27 | { | |
28 | struct stat buf; | |
29 | ||
30 | int ret = stat(SYSTEM_VERSION_COMPAT_PLIST_PATH, &buf); | |
31 | int error = errno; | |
32 | if (ret != 0) { | |
33 | if (error == ENOENT) { | |
34 | T_SKIP("no SystemVersionCompat.plist on this system in %s, skipping test...", | |
35 | SYSTEM_VERSION_COMPAT_PLIST_PATH); | |
36 | } else { | |
37 | T_ASSERT_FAIL("failed to find SystemVersionCompat.plist at " IOS_SYSTEM_VERSION_PLIST_PATH "with error: %s", | |
38 | strerror(error)); | |
39 | } | |
40 | } | |
41 | } | |
42 | ||
43 | static void | |
44 | check_ios_version_plist_exists(void) | |
45 | { | |
46 | struct stat buf; | |
47 | ||
48 | int ret = stat(IOS_SYSTEM_VERSION_PLIST_PATH, &buf); | |
49 | int error = errno; | |
50 | if (ret != 0) { | |
51 | if (errno == ENOENT) { | |
52 | T_SKIP("no iOSSystemVersion.plist on this system in %s, skipping test...", | |
53 | IOS_SYSTEM_VERSION_PLIST_PATH); | |
54 | } else { | |
55 | T_ASSERT_FAIL("failed to find iOSSystemVersion.plist at " IOS_SYSTEM_VERSION_PLIST_PATH "with error: %s", | |
56 | strerror(error)); | |
57 | } | |
58 | } | |
59 | } | |
60 | ||
61 | static void | |
62 | read_plist_version_info(char **version_plist_vers, char **compat_version_plist_vers, bool expect_shim) | |
63 | { | |
64 | char opened_path[MAXPATHLEN] = { '\0' }; | |
65 | ||
66 | int version_plist_fd = open(SYSTEM_VERSION_PLIST_PATH, O_RDONLY); | |
67 | T_QUIET; T_WITH_ERRNO; T_ASSERT_GT(version_plist_fd, 0, "opened %s", SYSTEM_VERSION_PLIST_PATH); | |
68 | ||
69 | // Resolve the full path of the file we've opened, verify it was either shimmed or not (as expected) | |
70 | int ret = fcntl(version_plist_fd, F_GETPATH, opened_path); | |
71 | T_QUIET; T_WITH_ERRNO; T_EXPECT_NE(ret, -1, "F_GETPATH on opened SystemVersion.plist"); | |
72 | if (ret != -1) { | |
73 | size_t opened_path_strlen = strlen(opened_path); | |
74 | if (expect_shim) { | |
75 | T_QUIET; T_EXPECT_GE(opened_path_strlen, strlen(SYSTEM_VERSION_COMPAT_PLIST_PATH), "opened path string length"); | |
76 | T_EXPECT_EQ_STR(SYSTEM_VERSION_COMPAT_PLIST_PATH, (const char *)&opened_path[(opened_path_strlen - strlen(SYSTEM_VERSION_COMPAT_PLIST_PATH))], | |
77 | "opened file path shimmed (Mac OS)"); | |
78 | } else { | |
79 | T_QUIET; T_EXPECT_GE(opened_path_strlen, strlen(SYSTEM_VERSION_PLIST_PATH), "opened path string length"); | |
80 | T_EXPECT_EQ_STR(SYSTEM_VERSION_PLIST_PATH, (const char *)&opened_path[(opened_path_strlen - strlen(SYSTEM_VERSION_PLIST_PATH))], | |
81 | "opened file path not shimmed"); | |
82 | } | |
83 | } | |
84 | ||
85 | // Read and parse the plists | |
86 | dispatch_semaphore_t sema = dispatch_semaphore_create(0); | |
87 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); | |
88 | xpc_create_from_plist_descriptor(version_plist_fd, queue, ^(xpc_object_t object) { | |
89 | if (object == NULL) { | |
90 | T_ASSERT_FAIL("Failed to parse dictionary from %s", SYSTEM_VERSION_PLIST_PATH); | |
91 | } | |
92 | if (xpc_get_type(object) != XPC_TYPE_DICTIONARY) { | |
93 | T_ASSERT_FAIL("%s does not contain dictionary plist", SYSTEM_VERSION_PLIST_PATH); | |
94 | } | |
95 | ||
96 | const char *plist_version = xpc_dictionary_get_string(object, PRODUCT_VERSION_KEY); | |
97 | if (plist_version) { | |
98 | T_LOG("Found %s for %s from %s", plist_version, PRODUCT_VERSION_KEY, SYSTEM_VERSION_PLIST_PATH); | |
99 | *version_plist_vers = strdup(plist_version); | |
100 | } | |
101 | dispatch_semaphore_signal(sema); | |
102 | }); | |
103 | dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); | |
104 | ||
105 | close(version_plist_fd); | |
106 | version_plist_fd = -1; | |
107 | ||
108 | int compat_version_plist_fd = open(SYSTEM_VERSION_COMPAT_PLIST_PATH, O_RDONLY); | |
109 | T_QUIET; T_WITH_ERRNO; T_ASSERT_GT(compat_version_plist_fd, 0, "opened %s", SYSTEM_VERSION_COMPAT_PLIST_PATH); | |
110 | ||
111 | xpc_create_from_plist_descriptor(compat_version_plist_fd, queue, ^(xpc_object_t object) { | |
112 | if (object == NULL) { | |
113 | T_ASSERT_FAIL("Failed to parse dictionary from %s", SYSTEM_VERSION_COMPAT_PLIST_PATH); | |
114 | } | |
115 | if (xpc_get_type(object) != XPC_TYPE_DICTIONARY) { | |
116 | T_ASSERT_FAIL("%s does not contain dictionary plist", SYSTEM_VERSION_COMPAT_PLIST_PATH); | |
117 | } | |
118 | ||
119 | const char *plist_version = xpc_dictionary_get_string(object, PRODUCT_VERSION_KEY); | |
120 | if (plist_version) { | |
121 | T_LOG("Found %s for %s from %s", plist_version, PRODUCT_VERSION_KEY, SYSTEM_VERSION_COMPAT_PLIST_PATH); | |
122 | *compat_version_plist_vers = strdup(plist_version); | |
123 | } | |
124 | dispatch_semaphore_signal(sema); | |
125 | }); | |
126 | dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); | |
127 | ||
128 | close(compat_version_plist_fd); | |
129 | compat_version_plist_fd = -1; | |
130 | ||
131 | return; | |
132 | } | |
133 | ||
134 | static void | |
135 | read_sysctl_version_info(char **vers, char **compat_vers) | |
136 | { | |
137 | char version[16] = { '\0' }, compat_version[16] = { '\0' }; | |
138 | size_t version_len = sizeof(version), compat_version_len = sizeof(compat_version); | |
139 | ||
140 | T_QUIET; T_ASSERT_POSIX_ZERO(sysctlbyname(PRODUCT_VERSION_SYSCTL, version, &version_len, NULL, 0), "read %s", PRODUCT_VERSION_SYSCTL); | |
141 | T_LOG("Foundd %s from %s", version, PRODUCT_VERSION_SYSCTL); | |
142 | ||
143 | T_QUIET; T_ASSERT_POSIX_ZERO(sysctlbyname(PRODUCT_VERSION_COMPAT_SYSCTL, compat_version, &compat_version_len, NULL, 0), | |
144 | "read %s", PRODUCT_VERSION_COMPAT_SYSCTL); | |
145 | T_LOG("Found %s from %s", compat_version, PRODUCT_VERSION_COMPAT_SYSCTL); | |
146 | ||
147 | *vers = strdup(version); | |
148 | *compat_vers = strdup(compat_version); | |
149 | ||
150 | return; | |
151 | } | |
152 | #endif // TARGET_OS_OSX | |
153 | ||
154 | T_DECL(test_system_version_compat_disabled, | |
155 | "Tests reading system product information without system version compat enabled") | |
156 | { | |
157 | #if TARGET_OS_OSX | |
158 | check_system_version_compat_plist_exists(); | |
159 | char *plist_vers = NULL, *plist_compat_vers = NULL; | |
160 | char *sysctl_vers = NULL, *sysctl_compat_vers = NULL; | |
161 | ||
162 | // Read plist version data | |
163 | read_plist_version_info(&plist_vers, &plist_compat_vers, false); | |
164 | ||
165 | // Read sysctl version data | |
166 | read_sysctl_version_info(&sysctl_vers, &sysctl_compat_vers); | |
167 | ||
168 | // Verify the normal data matches | |
169 | T_EXPECT_EQ_STR(plist_vers, sysctl_vers, "%s %s matches %s value", SYSTEM_VERSION_PLIST_PATH, | |
170 | PRODUCT_VERSION_KEY, PRODUCT_VERSION_SYSCTL); | |
171 | ||
172 | // Verify that the compatibility data matches | |
173 | T_EXPECT_EQ_STR(plist_compat_vers, sysctl_compat_vers, "%s %s matches %s value", SYSTEM_VERSION_COMPAT_PLIST_PATH, | |
174 | PRODUCT_VERSION_KEY, PRODUCT_VERSION_COMPAT_SYSCTL); | |
175 | ||
176 | ||
177 | free(plist_vers); | |
178 | free(plist_compat_vers); | |
179 | free(sysctl_vers); | |
180 | free(sysctl_compat_vers); | |
181 | ||
182 | T_PASS("verified version information without system version compat"); | |
183 | #else // TARGET_OS_OSX | |
184 | T_SKIP("system version compat only supported on macOS"); | |
185 | #endif // TARGET_OS_OSX | |
186 | } | |
187 | ||
188 | T_DECL(test_system_version_compat_enabled, | |
189 | "Tests reading system product information with system version compat enabled", | |
190 | T_META_ENVVAR("SYSTEM_VERSION_COMPAT=1")) | |
191 | { | |
192 | #if TARGET_OS_OSX | |
193 | check_system_version_compat_plist_exists(); | |
194 | char *plist_vers = NULL, *plist_compat_vers = NULL; | |
195 | char *sysctl_vers = NULL, *sysctl_compat_vers = NULL; | |
196 | ||
197 | // Read plist version data | |
198 | read_plist_version_info(&plist_vers, &plist_compat_vers, true); | |
199 | ||
200 | // Read sysctl version data | |
201 | read_sysctl_version_info(&sysctl_vers, &sysctl_compat_vers); | |
202 | ||
203 | // The version information should match from all sources with the shim enabled | |
204 | ||
205 | // Verify the normal data matches | |
206 | T_EXPECT_EQ_STR(plist_vers, sysctl_vers, "%s %s matches %s value", SYSTEM_VERSION_PLIST_PATH, | |
207 | PRODUCT_VERSION_KEY, PRODUCT_VERSION_SYSCTL); | |
208 | ||
209 | // Verify that the compatibility data matches | |
210 | T_EXPECT_EQ_STR(plist_compat_vers, sysctl_compat_vers, "%s %s matches %s value", SYSTEM_VERSION_COMPAT_PLIST_PATH, | |
211 | PRODUCT_VERSION_KEY, PRODUCT_VERSION_COMPAT_SYSCTL); | |
212 | ||
213 | // Verify the normal data matches the compatibility data | |
214 | T_EXPECT_EQ_STR(plist_vers, plist_compat_vers, "%s matches in both %s and %s", PRODUCT_VERSION_KEY, | |
215 | SYSTEM_VERSION_PLIST_PATH, SYSTEM_VERSION_COMPAT_PLIST_PATH); | |
216 | ||
217 | free(plist_vers); | |
218 | free(plist_compat_vers); | |
219 | free(sysctl_vers); | |
220 | free(sysctl_compat_vers); | |
221 | ||
222 | T_PASS("verified version information with Mac OS X shim enabled"); | |
223 | #else // TARGET_OS_OSX | |
224 | T_SKIP("system version compat only supported on macOS"); | |
225 | #endif // TARGET_OS_OSX | |
226 | } | |
227 | ||
228 | T_DECL(test_system_version_compat_enabled_ios, | |
229 | "Tests reading system product information with the iOS system version compat shim enabled", | |
230 | T_META_ENVVAR("SYSTEM_VERSION_COMPAT=2")) | |
231 | { | |
232 | #if TARGET_OS_OSX | |
233 | char opened_path[MAXPATHLEN] = { '\0' }; | |
234 | ||
235 | check_ios_version_plist_exists(); | |
236 | ||
237 | // Read out the ProductVersion from SystemVersion.plist and ensure that it contains the same value as the | |
238 | // iOSSupportVersion key | |
239 | ||
240 | __block char *read_plist_vers = NULL, *read_ios_support_version = NULL; | |
241 | ||
242 | int version_plist_fd = open(SYSTEM_VERSION_PLIST_PATH, O_RDONLY); | |
243 | T_QUIET; T_WITH_ERRNO; T_ASSERT_GT(version_plist_fd, 0, "opened %s", SYSTEM_VERSION_PLIST_PATH); | |
244 | ||
245 | // Resolve the full path of the file we've opened, verify it was shimmed as expected | |
246 | int ret = fcntl(version_plist_fd, F_GETPATH, opened_path); | |
247 | T_QUIET; T_WITH_ERRNO; T_EXPECT_NE(ret, -1, "F_GETPATH on opened SystemVersion.plist"); | |
248 | if (ret != -1) { | |
249 | size_t opened_path_strlen = strlen(opened_path); | |
250 | T_QUIET; T_EXPECT_GE(opened_path_strlen, strlen(IOS_SYSTEM_VERSION_PLIST_PATH), "opened path string length"); | |
251 | T_EXPECT_EQ_STR(IOS_SYSTEM_VERSION_PLIST_PATH, (const char *)&opened_path[(opened_path_strlen - strlen(IOS_SYSTEM_VERSION_PLIST_PATH))], | |
252 | "opened file path shimmed (iOS)"); | |
253 | } | |
254 | ||
255 | // Read and parse the attributes from the SystemVersion plist | |
256 | dispatch_semaphore_t sema = dispatch_semaphore_create(0); | |
257 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); | |
258 | xpc_create_from_plist_descriptor(version_plist_fd, queue, ^(xpc_object_t object) { | |
259 | if (object == NULL) { | |
260 | T_ASSERT_FAIL("Failed to parse dictionary from %s", SYSTEM_VERSION_PLIST_PATH); | |
261 | } | |
262 | if (xpc_get_type(object) != XPC_TYPE_DICTIONARY) { | |
263 | T_ASSERT_FAIL("%s does not contain dictionary plist", SYSTEM_VERSION_PLIST_PATH); | |
264 | } | |
265 | ||
266 | const char *plist_version = xpc_dictionary_get_string(object, PRODUCT_VERSION_KEY); | |
267 | if (plist_version) { | |
268 | T_LOG("Found %s for %s from %s", plist_version, PRODUCT_VERSION_KEY, SYSTEM_VERSION_PLIST_PATH); | |
269 | read_plist_vers = strdup(plist_version); | |
270 | } | |
271 | ||
272 | const char *ios_support_version = xpc_dictionary_get_string(object, IOS_SUPPORT_VERSION_KEY); | |
273 | if (ios_support_version) { | |
274 | T_LOG("Found %s for %s from %s", ios_support_version, IOS_SUPPORT_VERSION_KEY, SYSTEM_VERSION_PLIST_PATH); | |
275 | read_ios_support_version = strdup(ios_support_version); | |
276 | } | |
277 | ||
278 | dispatch_semaphore_signal(sema); | |
279 | }); | |
280 | dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); | |
281 | ||
282 | close(version_plist_fd); | |
283 | version_plist_fd = -1; | |
284 | ||
285 | // Verify the data matches | |
286 | T_EXPECT_EQ_STR(read_plist_vers, read_ios_support_version, "%s %s matches %s value", SYSTEM_VERSION_PLIST_PATH, | |
287 | PRODUCT_VERSION_KEY, IOS_SUPPORT_VERSION_KEY); | |
288 | ||
289 | T_PASS("verified version information with iOS shim enabled"); | |
290 | ||
291 | #else // TARGET_OS_OSX | |
292 | T_SKIP("iOS system version shim only supported on macOS"); | |
293 | #endif // TARGET_OS_OSX | |
294 | } |