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