1 #include <darwintest.h>
2 #include <darwintest_utils.h>
3 #include <dispatch/dispatch.h>
6 #include <TargetConditionals.h>
8 #include <xpc/private.h>
10 #define SYSTEM_VERSION_COMPAT_PLIST_PATH "/System/Library/CoreServices/SystemVersionCompat.plist"
11 #define IOS_SYSTEM_VERSION_PLIST_PATH "/System/Library/CoreServices/iOSSystemVersion.plist"
13 #define SYSTEM_VERSION_PLIST_FILENAME "SystemVersion.plist"
14 #define SYSTEM_VERSION_PLIST_PATH ("/System/Library/CoreServices/" SYSTEM_VERSION_PLIST_FILENAME)
16 #define PRODUCT_VERSION_KEY "ProductVersion"
17 #define IOS_SUPPORT_VERSION_KEY "iOSSupportVersion"
19 #define PRODUCT_VERSION_SYSCTL "kern.osproductversion"
20 #define PRODUCT_VERSION_COMPAT_SYSCTL "kern.osproductversioncompat"
22 T_GLOBAL_META(T_META_CHECK_LEAKS(false));
26 check_system_version_compat_plist_exists(void)
30 int ret
= stat(SYSTEM_VERSION_COMPAT_PLIST_PATH
, &buf
);
33 if (error
== ENOENT
) {
34 T_SKIP("no SystemVersionCompat.plist on this system in %s, skipping test...",
35 SYSTEM_VERSION_COMPAT_PLIST_PATH
);
37 T_ASSERT_FAIL("failed to find SystemVersionCompat.plist at " IOS_SYSTEM_VERSION_PLIST_PATH
"with error: %s",
44 check_ios_version_plist_exists(void)
48 int ret
= stat(IOS_SYSTEM_VERSION_PLIST_PATH
, &buf
);
51 if (errno
== ENOENT
) {
52 T_SKIP("no iOSSystemVersion.plist on this system in %s, skipping test...",
53 IOS_SYSTEM_VERSION_PLIST_PATH
);
55 T_ASSERT_FAIL("failed to find iOSSystemVersion.plist at " IOS_SYSTEM_VERSION_PLIST_PATH
"with error: %s",
62 read_plist_version_info(char **version_plist_vers
, char **compat_version_plist_vers
, bool expect_shim
)
64 char opened_path
[MAXPATHLEN
] = { '\0' };
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
);
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");
73 size_t opened_path_strlen
= strlen(opened_path
);
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)");
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");
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
) {
90 T_ASSERT_FAIL("Failed to parse dictionary from %s", SYSTEM_VERSION_PLIST_PATH
);
92 if (xpc_get_type(object
) != XPC_TYPE_DICTIONARY
) {
93 T_ASSERT_FAIL("%s does not contain dictionary plist", SYSTEM_VERSION_PLIST_PATH
);
96 const char *plist_version
= xpc_dictionary_get_string(object
, PRODUCT_VERSION_KEY
);
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
);
101 dispatch_semaphore_signal(sema
);
103 dispatch_semaphore_wait(sema
, DISPATCH_TIME_FOREVER
);
105 close(version_plist_fd
);
106 version_plist_fd
= -1;
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
);
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
);
115 if (xpc_get_type(object
) != XPC_TYPE_DICTIONARY
) {
116 T_ASSERT_FAIL("%s does not contain dictionary plist", SYSTEM_VERSION_COMPAT_PLIST_PATH
);
119 const char *plist_version
= xpc_dictionary_get_string(object
, PRODUCT_VERSION_KEY
);
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
);
124 dispatch_semaphore_signal(sema
);
126 dispatch_semaphore_wait(sema
, DISPATCH_TIME_FOREVER
);
128 close(compat_version_plist_fd
);
129 compat_version_plist_fd
= -1;
135 read_sysctl_version_info(char **vers
, char **compat_vers
)
137 char version
[16] = { '\0' }, compat_version
[16] = { '\0' };
138 size_t version_len
= sizeof(version
), compat_version_len
= sizeof(compat_version
);
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
);
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
);
147 *vers
= strdup(version
);
148 *compat_vers
= strdup(compat_version
);
152 #endif // TARGET_OS_OSX
154 T_DECL(test_system_version_compat_disabled
,
155 "Tests reading system product information without system version compat enabled")
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
;
162 // Read plist version data
163 read_plist_version_info(&plist_vers
, &plist_compat_vers
, false);
165 // Read sysctl version data
166 read_sysctl_version_info(&sysctl_vers
, &sysctl_compat_vers
);
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
);
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
);
178 free(plist_compat_vers
);
180 free(sysctl_compat_vers
);
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
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"))
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
;
197 // Read plist version data
198 read_plist_version_info(&plist_vers
, &plist_compat_vers
, true);
200 // Read sysctl version data
201 read_sysctl_version_info(&sysctl_vers
, &sysctl_compat_vers
);
203 // The version information should match from all sources with the shim enabled
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
);
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
);
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
);
218 free(plist_compat_vers
);
220 free(sysctl_compat_vers
);
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
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"))
233 char opened_path
[MAXPATHLEN
] = { '\0' };
235 check_ios_version_plist_exists();
237 // Read out the ProductVersion from SystemVersion.plist and ensure that it contains the same value as the
238 // iOSSupportVersion key
240 __block
char *read_plist_vers
= NULL
, *read_ios_support_version
= NULL
;
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
);
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");
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)");
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
);
262 if (xpc_get_type(object
) != XPC_TYPE_DICTIONARY
) {
263 T_ASSERT_FAIL("%s does not contain dictionary plist", SYSTEM_VERSION_PLIST_PATH
);
266 const char *plist_version
= xpc_dictionary_get_string(object
, PRODUCT_VERSION_KEY
);
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
);
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
);
278 dispatch_semaphore_signal(sema
);
280 dispatch_semaphore_wait(sema
, DISPATCH_TIME_FOREVER
);
282 close(version_plist_fd
);
283 version_plist_fd
= -1;
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
);
289 T_PASS("verified version information with iOS shim enabled");
291 #else // TARGET_OS_OSX
292 T_SKIP("iOS system version shim only supported on macOS");
293 #endif // TARGET_OS_OSX