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