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