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
_initializeAvailabilityCheck(bool LoadPlist)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.
compatibilityInitializeAvailabilityCheck(void * Unused)240 static void compatibilityInitializeAvailabilityCheck(void *Unused) {
241 (void)Unused;
242 _initializeAvailabilityCheck(/*LoadPlist=*/true);
243 }
244
initializeAvailabilityCheck(void * Unused)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.
__isOSVersionAtLeast(int32_t Major,int32_t Minor,int32_t Subminor)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
ConstructVersion(uint32_t Major,uint32_t Minor,uint32_t Subminor)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
__isPlatformVersionAtLeast(uint32_t Platform,uint32_t Major,uint32_t Minor,uint32_t Subminor)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
__isPlatformOrVariantPlatformVersionAtLeast(uint32_t Platform,uint32_t Major,uint32_t Minor,uint32_t Subminor,uint32_t Platform2,uint32_t Major2,uint32_t Minor2,uint32_t Subminor2)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
readSystemProperties(void)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
__isOSVersionAtLeast(int32_t Major,int32_t Minor,int32_t Subminor)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