xref: /llvm-project/compiler-rt/lib/builtins/os_version_check.c (revision 9808f484534f503e9975d367082ff6933676ca20)
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 
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.
240 static void compatibilityInitializeAvailabilityCheck(void *Unused) {
241   (void)Unused;
242   _initializeAvailabilityCheck(/*LoadPlist=*/true);
243 }
244 
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.
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 
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 
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 
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 
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 
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