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 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.
235 static void compatibilityInitializeAvailabilityCheck(void *Unused) {
236   (void)Unused;
237   _initializeAvailabilityCheck(/*LoadPlist=*/true);
238 }
239 
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.
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 
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 
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 
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 
309 int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) {
310   (void) Minor;
311   (void) 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