355 lines · c
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-exception6//7//===----------------------------------------------------------------------===//8//9// This file implements the function __isOSVersionAtLeast, used by10// Objective-C's @available11//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.26static int32_t GlobalMajor, GlobalMinor, GlobalSubminor;27static dispatch_once_t DispatchOnceCounter;28static dispatch_once_t CompatibilityDispatchOnceCounter;29 30// _availability_version_check darwin API support.31typedef uint32_t dyld_platform_t;32 33typedef struct {34 dyld_platform_t platform;35 uint32_t version;36} dyld_build_version_t;37 38typedef bool (*AvailabilityVersionCheckFuncTy)(uint32_t count,39 dyld_build_version_t versions[]);40 41static AvailabilityVersionCheckFuncTy AvailabilityVersionCheck;42 43// We can't include <CoreFoundation/CoreFoundation.h> directly from here, so44// just forward declare everything that we need from it.45 46typedef const void *CFDataRef, *CFAllocatorRef, *CFPropertyListRef,47 *CFStringRef, *CFDictionaryRef, *CFTypeRef, *CFErrorRef;48 49#if __LLP64__50typedef unsigned long long CFTypeID;51typedef unsigned long long CFOptionFlags;52typedef signed long long CFIndex;53#else54typedef unsigned long CFTypeID;55typedef unsigned long CFOptionFlags;56typedef signed long CFIndex;57#endif58 59typedef unsigned char UInt8;60typedef _Bool Boolean;61typedef CFIndex CFPropertyListFormat;62typedef uint32_t CFStringEncoding;63 64// kCFStringEncodingASCII analog.65#define CF_STRING_ENCODING_ASCII 0x060066// kCFStringEncodingUTF8 analog.67#define CF_STRING_ENCODING_UTF8 0x0800010068#define CF_PROPERTY_LIST_IMMUTABLE 069 70typedef CFDataRef (*CFDataCreateWithBytesNoCopyFuncTy)(CFAllocatorRef,71 const UInt8 *, CFIndex,72 CFAllocatorRef);73typedef CFPropertyListRef (*CFPropertyListCreateWithDataFuncTy)(74 CFAllocatorRef, CFDataRef, CFOptionFlags, CFPropertyListFormat *,75 CFErrorRef *);76typedef CFPropertyListRef (*CFPropertyListCreateFromXMLDataFuncTy)(77 CFAllocatorRef, CFDataRef, CFOptionFlags, CFStringRef *);78typedef CFStringRef (*CFStringCreateWithCStringNoCopyFuncTy)(CFAllocatorRef,79 const char *,80 CFStringEncoding,81 CFAllocatorRef);82typedef const void *(*CFDictionaryGetValueFuncTy)(CFDictionaryRef,83 const void *);84typedef CFTypeID (*CFGetTypeIDFuncTy)(CFTypeRef);85typedef CFTypeID (*CFStringGetTypeIDFuncTy)(void);86typedef Boolean (*CFStringGetCStringFuncTy)(CFStringRef, char *, CFIndex,87 CFStringEncoding);88typedef void (*CFReleaseFuncTy)(CFTypeRef);89 90extern __attribute__((weak_import))91bool _availability_version_check(uint32_t count,92 dyld_build_version_t versions[]);93 94static 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 to111 // __isOSVersionAtLeast still work even with new compiler-rt and old OSes.112 113 // Load CoreFoundation dynamically114 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 it127// will be NULL on earlier OS versions.128#pragma clang diagnostic push129#pragma clang diagnostic ignored "-Wdeprecated-declarations"130 CFPropertyListCreateFromXMLDataFuncTy CFPropertyListCreateFromXMLDataFunc =131 (CFPropertyListCreateFromXMLDataFuncTy)dlsym(132 RTLD_DEFAULT, "CFPropertyListCreateFromXMLData");133#pragma clang diagnostic pop134 // CFPropertyListCreateFromXMLDataFunc is deprecated in macOS 10.10, so it135 // 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_SIMULATOR167 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#endif175 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 ourselves200 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 else209 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 230Fail: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.240static void compatibilityInitializeAvailabilityCheck(void *Unused) {241 (void)Unused;242 _initializeAvailabilityCheck(/*LoadPlist=*/true);243}244 245static 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 need251// to keep it around to ensure that object files that reference it are still252// usable when linked with new compiler-rt.253int32_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 269static 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 1275 276int32_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_OSX289 290int32_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#endif310 311#elif __ANDROID__312 313#include <pthread.h>314#include <stdlib.h>315#include <string.h>316#include <sys/system_properties.h>317 318static int SdkVersion;319static int IsPreRelease;320 321static 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 339int32_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#else350 351// Silence an empty translation unit warning.352typedef int unused;353 354#endif355