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