1 | //===-- os_version_check.c - OS version checking -------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file implements the function __isOSVersionAtLeast, used by |
10 | // Objective-C's @available |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #ifdef __APPLE__ |
15 | |
16 | #include <TargetConditionals.h> |
17 | #include <assert.h> |
18 | #include <dispatch/dispatch.h> |
19 | #include <dlfcn.h> |
20 | #include <stdint.h> |
21 | #include <stdio.h> |
22 | #include <stdlib.h> |
23 | #include <string.h> |
24 | |
25 | // These three variables hold the host's OS version. |
26 | static int32_t GlobalMajor, GlobalMinor, GlobalSubminor; |
27 | static dispatch_once_t DispatchOnceCounter; |
28 | static dispatch_once_t CompatibilityDispatchOnceCounter; |
29 | |
30 | // _availability_version_check darwin API support. |
31 | typedef uint32_t dyld_platform_t; |
32 | |
33 | typedef struct { |
34 | dyld_platform_t platform; |
35 | uint32_t version; |
36 | } dyld_build_version_t; |
37 | |
38 | typedef bool (*AvailabilityVersionCheckFuncTy)(uint32_t count, |
39 | dyld_build_version_t versions[]); |
40 | |
41 | static AvailabilityVersionCheckFuncTy AvailabilityVersionCheck; |
42 | |
43 | // We can't include <CoreFoundation/CoreFoundation.h> directly from here, so |
44 | // just forward declare everything that we need from it. |
45 | |
46 | typedef const void *CFDataRef, *CFAllocatorRef, *CFPropertyListRef, |
47 | *CFStringRef, *CFDictionaryRef, *CFTypeRef, *CFErrorRef; |
48 | |
49 | #if __LLP64__ |
50 | typedef unsigned long long CFTypeID; |
51 | typedef unsigned long long CFOptionFlags; |
52 | typedef signed long long CFIndex; |
53 | #else |
54 | typedef unsigned long CFTypeID; |
55 | typedef unsigned long CFOptionFlags; |
56 | typedef signed long CFIndex; |
57 | #endif |
58 | |
59 | typedef unsigned char UInt8; |
60 | typedef _Bool Boolean; |
61 | typedef CFIndex CFPropertyListFormat; |
62 | typedef uint32_t CFStringEncoding; |
63 | |
64 | // kCFStringEncodingASCII analog. |
65 | #define CF_STRING_ENCODING_ASCII 0x0600 |
66 | // kCFStringEncodingUTF8 analog. |
67 | #define CF_STRING_ENCODING_UTF8 0x08000100 |
68 | #define CF_PROPERTY_LIST_IMMUTABLE 0 |
69 | |
70 | typedef CFDataRef (*CFDataCreateWithBytesNoCopyFuncTy)(CFAllocatorRef, |
71 | const UInt8 *, CFIndex, |
72 | CFAllocatorRef); |
73 | typedef CFPropertyListRef (*CFPropertyListCreateWithDataFuncTy)( |
74 | CFAllocatorRef, CFDataRef, CFOptionFlags, CFPropertyListFormat *, |
75 | CFErrorRef *); |
76 | typedef CFPropertyListRef (*CFPropertyListCreateFromXMLDataFuncTy)( |
77 | CFAllocatorRef, CFDataRef, CFOptionFlags, CFStringRef *); |
78 | typedef CFStringRef (*CFStringCreateWithCStringNoCopyFuncTy)(CFAllocatorRef, |
79 | const char *, |
80 | CFStringEncoding, |
81 | CFAllocatorRef); |
82 | typedef const void *(*CFDictionaryGetValueFuncTy)(CFDictionaryRef, |
83 | const void *); |
84 | typedef CFTypeID (*CFGetTypeIDFuncTy)(CFTypeRef); |
85 | typedef CFTypeID (*CFStringGetTypeIDFuncTy)(void); |
86 | typedef Boolean (*CFStringGetCStringFuncTy)(CFStringRef, char *, CFIndex, |
87 | CFStringEncoding); |
88 | typedef void (*CFReleaseFuncTy)(CFTypeRef); |
89 | |
90 | extern __attribute__((weak_import)) |
91 | bool _availability_version_check(uint32_t count, |
92 | dyld_build_version_t versions[]); |
93 | |
94 | static void _initializeAvailabilityCheck(bool LoadPlist) { |
95 | if (AvailabilityVersionCheck && !LoadPlist) { |
96 | // New API is supported and we're not being asked to load the plist, |
97 | // exit early! |
98 | return; |
99 | } |
100 | |
101 | // Use the new API if it's is available. |
102 | if (_availability_version_check) |
103 | AvailabilityVersionCheck = &_availability_version_check; |
104 | |
105 | if (AvailabilityVersionCheck && !LoadPlist) { |
106 | // New API is supported and we're not being asked to load the plist, |
107 | // exit early! |
108 | return; |
109 | } |
110 | // Still load the PLIST to ensure that the existing calls to |
111 | // __isOSVersionAtLeast still work even with new compiler-rt and old OSes. |
112 | |
113 | // Load CoreFoundation dynamically |
114 | const void *NullAllocator = dlsym(RTLD_DEFAULT, "kCFAllocatorNull" ); |
115 | if (!NullAllocator) |
116 | return; |
117 | const CFAllocatorRef AllocatorNull = *(const CFAllocatorRef *)NullAllocator; |
118 | CFDataCreateWithBytesNoCopyFuncTy CFDataCreateWithBytesNoCopyFunc = |
119 | (CFDataCreateWithBytesNoCopyFuncTy)dlsym(RTLD_DEFAULT, |
120 | "CFDataCreateWithBytesNoCopy" ); |
121 | if (!CFDataCreateWithBytesNoCopyFunc) |
122 | return; |
123 | CFPropertyListCreateWithDataFuncTy CFPropertyListCreateWithDataFunc = |
124 | (CFPropertyListCreateWithDataFuncTy)dlsym(RTLD_DEFAULT, |
125 | "CFPropertyListCreateWithData" ); |
126 | // CFPropertyListCreateWithData was introduced only in macOS 10.6+, so it |
127 | // will be NULL on earlier OS versions. |
128 | #pragma clang diagnostic push |
129 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
130 | CFPropertyListCreateFromXMLDataFuncTy CFPropertyListCreateFromXMLDataFunc = |
131 | (CFPropertyListCreateFromXMLDataFuncTy)dlsym( |
132 | RTLD_DEFAULT, "CFPropertyListCreateFromXMLData" ); |
133 | #pragma clang diagnostic pop |
134 | // CFPropertyListCreateFromXMLDataFunc is deprecated in macOS 10.10, so it |
135 | // might be NULL in future OS versions. |
136 | if (!CFPropertyListCreateWithDataFunc && !CFPropertyListCreateFromXMLDataFunc) |
137 | return; |
138 | CFStringCreateWithCStringNoCopyFuncTy CFStringCreateWithCStringNoCopyFunc = |
139 | (CFStringCreateWithCStringNoCopyFuncTy)dlsym( |
140 | RTLD_DEFAULT, "CFStringCreateWithCStringNoCopy" ); |
141 | if (!CFStringCreateWithCStringNoCopyFunc) |
142 | return; |
143 | CFDictionaryGetValueFuncTy CFDictionaryGetValueFunc = |
144 | (CFDictionaryGetValueFuncTy)dlsym(RTLD_DEFAULT, "CFDictionaryGetValue" ); |
145 | if (!CFDictionaryGetValueFunc) |
146 | return; |
147 | CFGetTypeIDFuncTy CFGetTypeIDFunc = |
148 | (CFGetTypeIDFuncTy)dlsym(RTLD_DEFAULT, "CFGetTypeID" ); |
149 | if (!CFGetTypeIDFunc) |
150 | return; |
151 | CFStringGetTypeIDFuncTy CFStringGetTypeIDFunc = |
152 | (CFStringGetTypeIDFuncTy)dlsym(RTLD_DEFAULT, "CFStringGetTypeID" ); |
153 | if (!CFStringGetTypeIDFunc) |
154 | return; |
155 | CFStringGetCStringFuncTy CFStringGetCStringFunc = |
156 | (CFStringGetCStringFuncTy)dlsym(RTLD_DEFAULT, "CFStringGetCString" ); |
157 | if (!CFStringGetCStringFunc) |
158 | return; |
159 | CFReleaseFuncTy CFReleaseFunc = |
160 | (CFReleaseFuncTy)dlsym(RTLD_DEFAULT, "CFRelease" ); |
161 | if (!CFReleaseFunc) |
162 | return; |
163 | |
164 | char *PListPath = "/System/Library/CoreServices/SystemVersion.plist" ; |
165 | |
166 | #if TARGET_OS_SIMULATOR |
167 | char *PListPathPrefix = getenv("IPHONE_SIMULATOR_ROOT" ); |
168 | if (!PListPathPrefix) |
169 | return; |
170 | char FullPath[strlen(PListPathPrefix) + strlen(PListPath) + 1]; |
171 | strcpy(FullPath, PListPathPrefix); |
172 | strcat(FullPath, PListPath); |
173 | PListPath = FullPath; |
174 | #endif |
175 | FILE *PropertyList = fopen(PListPath, "r" ); |
176 | if (!PropertyList) |
177 | return; |
178 | |
179 | // Dynamically allocated stuff. |
180 | CFDictionaryRef PListRef = NULL; |
181 | CFDataRef FileContentsRef = NULL; |
182 | UInt8 *PListBuf = NULL; |
183 | |
184 | fseek(PropertyList, 0, SEEK_END); |
185 | long PListFileSize = ftell(PropertyList); |
186 | if (PListFileSize < 0) |
187 | goto Fail; |
188 | rewind(PropertyList); |
189 | |
190 | PListBuf = malloc((size_t)PListFileSize); |
191 | if (!PListBuf) |
192 | goto Fail; |
193 | |
194 | size_t NumRead = fread(PListBuf, 1, (size_t)PListFileSize, PropertyList); |
195 | if (NumRead != (size_t)PListFileSize) |
196 | goto Fail; |
197 | |
198 | // Get the file buffer into CF's format. We pass in a null allocator here * |
199 | // because we free PListBuf ourselves |
200 | FileContentsRef = (*CFDataCreateWithBytesNoCopyFunc)( |
201 | NULL, PListBuf, (CFIndex)NumRead, AllocatorNull); |
202 | if (!FileContentsRef) |
203 | goto Fail; |
204 | |
205 | if (CFPropertyListCreateWithDataFunc) |
206 | PListRef = (*CFPropertyListCreateWithDataFunc)( |
207 | NULL, FileContentsRef, CF_PROPERTY_LIST_IMMUTABLE, NULL, NULL); |
208 | else |
209 | PListRef = (*CFPropertyListCreateFromXMLDataFunc)( |
210 | NULL, FileContentsRef, CF_PROPERTY_LIST_IMMUTABLE, NULL); |
211 | if (!PListRef) |
212 | goto Fail; |
213 | |
214 | CFStringRef ProductVersion = (*CFStringCreateWithCStringNoCopyFunc)( |
215 | NULL, "ProductVersion" , CF_STRING_ENCODING_ASCII, AllocatorNull); |
216 | if (!ProductVersion) |
217 | goto Fail; |
218 | CFTypeRef OpaqueValue = (*CFDictionaryGetValueFunc)(PListRef, ProductVersion); |
219 | (*CFReleaseFunc)(ProductVersion); |
220 | if (!OpaqueValue || |
221 | (*CFGetTypeIDFunc)(OpaqueValue) != (*CFStringGetTypeIDFunc)()) |
222 | goto Fail; |
223 | |
224 | char VersionStr[32]; |
225 | if (!(*CFStringGetCStringFunc)((CFStringRef)OpaqueValue, VersionStr, |
226 | sizeof(VersionStr), CF_STRING_ENCODING_UTF8)) |
227 | goto Fail; |
228 | sscanf(VersionStr, "%d.%d.%d" , &GlobalMajor, &GlobalMinor, &GlobalSubminor); |
229 | |
230 | Fail: |
231 | if (PListRef) |
232 | (*CFReleaseFunc)(PListRef); |
233 | if (FileContentsRef) |
234 | (*CFReleaseFunc)(FileContentsRef); |
235 | free(PListBuf); |
236 | fclose(PropertyList); |
237 | } |
238 | |
239 | // Find and parse the SystemVersion.plist file. |
240 | static void compatibilityInitializeAvailabilityCheck(void *Unused) { |
241 | (void)Unused; |
242 | _initializeAvailabilityCheck(/*LoadPlist=*/true); |
243 | } |
244 | |
245 | static void initializeAvailabilityCheck(void *Unused) { |
246 | (void)Unused; |
247 | _initializeAvailabilityCheck(/*LoadPlist=*/false); |
248 | } |
249 | |
250 | // This old API entry point is no longer used by Clang for Darwin. We still need |
251 | // to keep it around to ensure that object files that reference it are still |
252 | // usable when linked with new compiler-rt. |
253 | int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) { |
254 | // Populate the global version variables, if they haven't already. |
255 | dispatch_once_f(&CompatibilityDispatchOnceCounter, NULL, |
256 | compatibilityInitializeAvailabilityCheck); |
257 | |
258 | if (Major < GlobalMajor) |
259 | return 1; |
260 | if (Major > GlobalMajor) |
261 | return 0; |
262 | if (Minor < GlobalMinor) |
263 | return 1; |
264 | if (Minor > GlobalMinor) |
265 | return 0; |
266 | return Subminor <= GlobalSubminor; |
267 | } |
268 | |
269 | static inline uint32_t ConstructVersion(uint32_t Major, uint32_t Minor, |
270 | uint32_t Subminor) { |
271 | return ((Major & 0xffff) << 16) | ((Minor & 0xff) << 8) | (Subminor & 0xff); |
272 | } |
273 | |
274 | #define PLATFORM_MACOS 1 |
275 | |
276 | int32_t __isPlatformVersionAtLeast(uint32_t Platform, uint32_t Major, |
277 | uint32_t Minor, uint32_t Subminor) { |
278 | dispatch_once_f(&DispatchOnceCounter, NULL, initializeAvailabilityCheck); |
279 | |
280 | if (!AvailabilityVersionCheck) { |
281 | return __isOSVersionAtLeast(Major, Minor, Subminor); |
282 | } |
283 | dyld_build_version_t Versions[] = { |
284 | {Platform, ConstructVersion(Major, Minor, Subminor)}}; |
285 | return AvailabilityVersionCheck(1, Versions); |
286 | } |
287 | |
288 | #if TARGET_OS_OSX |
289 | |
290 | int32_t __isPlatformOrVariantPlatformVersionAtLeast( |
291 | uint32_t Platform, uint32_t Major, uint32_t Minor, uint32_t Subminor, |
292 | uint32_t Platform2, uint32_t Major2, uint32_t Minor2, uint32_t Subminor2) { |
293 | dispatch_once_f(&DispatchOnceCounter, NULL, initializeAvailabilityCheck); |
294 | |
295 | if (!AvailabilityVersionCheck) { |
296 | // Handle case of back-deployment for older macOS. |
297 | if (Platform == PLATFORM_MACOS) { |
298 | return __isOSVersionAtLeast(Major, Minor, Subminor); |
299 | } |
300 | assert(Platform2 == PLATFORM_MACOS && "unexpected platform" ); |
301 | return __isOSVersionAtLeast(Major2, Minor2, Subminor2); |
302 | } |
303 | dyld_build_version_t Versions[] = { |
304 | {Platform, ConstructVersion(Major, Minor, Subminor)}, |
305 | {Platform2, ConstructVersion(Major2, Minor2, Subminor2)}}; |
306 | return AvailabilityVersionCheck(2, Versions); |
307 | } |
308 | |
309 | #endif |
310 | |
311 | #elif __ANDROID__ |
312 | |
313 | #include <pthread.h> |
314 | #include <stdlib.h> |
315 | #include <string.h> |
316 | #include <sys/system_properties.h> |
317 | |
318 | static int SdkVersion; |
319 | static int IsPreRelease; |
320 | |
321 | static void readSystemProperties(void) { |
322 | char buf[PROP_VALUE_MAX]; |
323 | |
324 | if (__system_property_get("ro.build.version.sdk" , buf) == 0) { |
325 | // When the system property doesn't exist, defaults to future API level. |
326 | SdkVersion = __ANDROID_API_FUTURE__; |
327 | } else { |
328 | SdkVersion = atoi(buf); |
329 | } |
330 | |
331 | if (__system_property_get("ro.build.version.codename" , buf) == 0) { |
332 | IsPreRelease = 1; |
333 | } else { |
334 | IsPreRelease = strcmp(buf, "REL" ) != 0; |
335 | } |
336 | return; |
337 | } |
338 | |
339 | int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) { |
340 | (void) Minor; |
341 | (void) Subminor; |
342 | static pthread_once_t once = PTHREAD_ONCE_INIT; |
343 | pthread_once(&once, readSystemProperties); |
344 | |
345 | // Allow all on pre-release. Note that we still rely on compile-time checks. |
346 | return SdkVersion >= Major || IsPreRelease; |
347 | } |
348 | |
349 | #else |
350 | |
351 | // Silence an empty translation unit warning. |
352 | typedef int unused; |
353 | |
354 | #endif |
355 | |