| 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 | |