//===-- os_version_check.c - OS version checking  -------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements the function __isOSVersionAtLeast, used by
// Objective-C's @available
//
//===----------------------------------------------------------------------===//

#ifdef __APPLE__

#include <TargetConditionals.h>
#include <assert.h>
#include <dispatch/dispatch.h>
#include <dlfcn.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// These three variables hold the host's OS version.
static int32_t GlobalMajor, GlobalMinor, GlobalSubminor;
static dispatch_once_t DispatchOnceCounter;
static dispatch_once_t CompatibilityDispatchOnceCounter;

// _availability_version_check darwin API support.
typedef uint32_t dyld_platform_t;

typedef struct {
  dyld_platform_t platform;
  uint32_t version;
} dyld_build_version_t;

typedef bool (*AvailabilityVersionCheckFuncTy)(uint32_t count,
                                               dyld_build_version_t versions[]);

static AvailabilityVersionCheckFuncTy AvailabilityVersionCheck;

// We can't include <CoreFoundation/CoreFoundation.h> directly from here, so
// just forward declare everything that we need from it.

typedef const void *CFDataRef, *CFAllocatorRef, *CFPropertyListRef,
    *CFStringRef, *CFDictionaryRef, *CFTypeRef, *CFErrorRef;

#if __LLP64__
typedef unsigned long long CFTypeID;
typedef unsigned long long CFOptionFlags;
typedef signed long long CFIndex;
#else
typedef unsigned long CFTypeID;
typedef unsigned long CFOptionFlags;
typedef signed long CFIndex;
#endif

typedef unsigned char UInt8;
typedef _Bool Boolean;
typedef CFIndex CFPropertyListFormat;
typedef uint32_t CFStringEncoding;

// kCFStringEncodingASCII analog.
#define CF_STRING_ENCODING_ASCII 0x0600
// kCFStringEncodingUTF8 analog.
#define CF_STRING_ENCODING_UTF8 0x08000100
#define CF_PROPERTY_LIST_IMMUTABLE 0

typedef CFDataRef (*CFDataCreateWithBytesNoCopyFuncTy)(CFAllocatorRef,
                                                       const UInt8 *, CFIndex,
                                                       CFAllocatorRef);
typedef CFPropertyListRef (*CFPropertyListCreateWithDataFuncTy)(
    CFAllocatorRef, CFDataRef, CFOptionFlags, CFPropertyListFormat *,
    CFErrorRef *);
typedef CFPropertyListRef (*CFPropertyListCreateFromXMLDataFuncTy)(
    CFAllocatorRef, CFDataRef, CFOptionFlags, CFStringRef *);
typedef CFStringRef (*CFStringCreateWithCStringNoCopyFuncTy)(CFAllocatorRef,
                                                             const char *,
                                                             CFStringEncoding,
                                                             CFAllocatorRef);
typedef const void *(*CFDictionaryGetValueFuncTy)(CFDictionaryRef,
                                                  const void *);
typedef CFTypeID (*CFGetTypeIDFuncTy)(CFTypeRef);
typedef CFTypeID (*CFStringGetTypeIDFuncTy)(void);
typedef Boolean (*CFStringGetCStringFuncTy)(CFStringRef, char *, CFIndex,
                                            CFStringEncoding);
typedef void (*CFReleaseFuncTy)(CFTypeRef);

extern __attribute__((weak_import))
bool _availability_version_check(uint32_t count,
                                 dyld_build_version_t versions[]);

static void _initializeAvailabilityCheck(bool LoadPlist) {
  if (AvailabilityVersionCheck && !LoadPlist) {
    // New API is supported and we're not being asked to load the plist,
    // exit early!
    return;
  }

  // Use the new API if it's is available.
  if (_availability_version_check)
    AvailabilityVersionCheck = &_availability_version_check;

  if (AvailabilityVersionCheck && !LoadPlist) {
    // New API is supported and we're not being asked to load the plist,
    // exit early!
    return;
  }
  // Still load the PLIST to ensure that the existing calls to
  // __isOSVersionAtLeast still work even with new compiler-rt and old OSes.

  // Load CoreFoundation dynamically
  const void *NullAllocator = dlsym(RTLD_DEFAULT, "kCFAllocatorNull");
  if (!NullAllocator)
    return;
  const CFAllocatorRef AllocatorNull = *(const CFAllocatorRef *)NullAllocator;
  CFDataCreateWithBytesNoCopyFuncTy CFDataCreateWithBytesNoCopyFunc =
      (CFDataCreateWithBytesNoCopyFuncTy)dlsym(RTLD_DEFAULT,
                                               "CFDataCreateWithBytesNoCopy");
  if (!CFDataCreateWithBytesNoCopyFunc)
    return;
  CFPropertyListCreateWithDataFuncTy CFPropertyListCreateWithDataFunc =
      (CFPropertyListCreateWithDataFuncTy)dlsym(RTLD_DEFAULT,
                                                "CFPropertyListCreateWithData");
// CFPropertyListCreateWithData was introduced only in macOS 10.6+, so it
// will be NULL on earlier OS versions.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  CFPropertyListCreateFromXMLDataFuncTy CFPropertyListCreateFromXMLDataFunc =
      (CFPropertyListCreateFromXMLDataFuncTy)dlsym(
          RTLD_DEFAULT, "CFPropertyListCreateFromXMLData");
#pragma clang diagnostic pop
  // CFPropertyListCreateFromXMLDataFunc is deprecated in macOS 10.10, so it
  // might be NULL in future OS versions.
  if (!CFPropertyListCreateWithDataFunc && !CFPropertyListCreateFromXMLDataFunc)
    return;
  CFStringCreateWithCStringNoCopyFuncTy CFStringCreateWithCStringNoCopyFunc =
      (CFStringCreateWithCStringNoCopyFuncTy)dlsym(
          RTLD_DEFAULT, "CFStringCreateWithCStringNoCopy");
  if (!CFStringCreateWithCStringNoCopyFunc)
    return;
  CFDictionaryGetValueFuncTy CFDictionaryGetValueFunc =
      (CFDictionaryGetValueFuncTy)dlsym(RTLD_DEFAULT, "CFDictionaryGetValue");
  if (!CFDictionaryGetValueFunc)
    return;
  CFGetTypeIDFuncTy CFGetTypeIDFunc =
      (CFGetTypeIDFuncTy)dlsym(RTLD_DEFAULT, "CFGetTypeID");
  if (!CFGetTypeIDFunc)
    return;
  CFStringGetTypeIDFuncTy CFStringGetTypeIDFunc =
      (CFStringGetTypeIDFuncTy)dlsym(RTLD_DEFAULT, "CFStringGetTypeID");
  if (!CFStringGetTypeIDFunc)
    return;
  CFStringGetCStringFuncTy CFStringGetCStringFunc =
      (CFStringGetCStringFuncTy)dlsym(RTLD_DEFAULT, "CFStringGetCString");
  if (!CFStringGetCStringFunc)
    return;
  CFReleaseFuncTy CFReleaseFunc =
      (CFReleaseFuncTy)dlsym(RTLD_DEFAULT, "CFRelease");
  if (!CFReleaseFunc)
    return;

  char *PListPath = "/System/Library/CoreServices/SystemVersion.plist";

#if TARGET_OS_SIMULATOR
  char *PListPathPrefix = getenv("IPHONE_SIMULATOR_ROOT");
  if (!PListPathPrefix)
    return;
  char FullPath[strlen(PListPathPrefix) + strlen(PListPath) + 1];
  strcpy(FullPath, PListPathPrefix);
  strcat(FullPath, PListPath);
  PListPath = FullPath;
#endif
  FILE *PropertyList = fopen(PListPath, "r");
  if (!PropertyList)
    return;

  // Dynamically allocated stuff.
  CFDictionaryRef PListRef = NULL;
  CFDataRef FileContentsRef = NULL;
  UInt8 *PListBuf = NULL;

  fseek(PropertyList, 0, SEEK_END);
  long PListFileSize = ftell(PropertyList);
  if (PListFileSize < 0)
    goto Fail;
  rewind(PropertyList);

  PListBuf = malloc((size_t)PListFileSize);
  if (!PListBuf)
    goto Fail;

  size_t NumRead = fread(PListBuf, 1, (size_t)PListFileSize, PropertyList);
  if (NumRead != (size_t)PListFileSize)
    goto Fail;

  // Get the file buffer into CF's format. We pass in a null allocator here *
  // because we free PListBuf ourselves
  FileContentsRef = (*CFDataCreateWithBytesNoCopyFunc)(
      NULL, PListBuf, (CFIndex)NumRead, AllocatorNull);
  if (!FileContentsRef)
    goto Fail;

  if (CFPropertyListCreateWithDataFunc)
    PListRef = (*CFPropertyListCreateWithDataFunc)(
        NULL, FileContentsRef, CF_PROPERTY_LIST_IMMUTABLE, NULL, NULL);
  else
    PListRef = (*CFPropertyListCreateFromXMLDataFunc)(
        NULL, FileContentsRef, CF_PROPERTY_LIST_IMMUTABLE, NULL);
  if (!PListRef)
    goto Fail;

  CFStringRef ProductVersion = (*CFStringCreateWithCStringNoCopyFunc)(
      NULL, "ProductVersion", CF_STRING_ENCODING_ASCII, AllocatorNull);
  if (!ProductVersion)
    goto Fail;
  CFTypeRef OpaqueValue = (*CFDictionaryGetValueFunc)(PListRef, ProductVersion);
  (*CFReleaseFunc)(ProductVersion);
  if (!OpaqueValue ||
      (*CFGetTypeIDFunc)(OpaqueValue) != (*CFStringGetTypeIDFunc)())
    goto Fail;

  char VersionStr[32];
  if (!(*CFStringGetCStringFunc)((CFStringRef)OpaqueValue, VersionStr,
                                 sizeof(VersionStr), CF_STRING_ENCODING_UTF8))
    goto Fail;
  sscanf(VersionStr, "%d.%d.%d", &GlobalMajor, &GlobalMinor, &GlobalSubminor);

Fail:
  if (PListRef)
    (*CFReleaseFunc)(PListRef);
  if (FileContentsRef)
    (*CFReleaseFunc)(FileContentsRef);
  free(PListBuf);
  fclose(PropertyList);
}

// Find and parse the SystemVersion.plist file.
static void compatibilityInitializeAvailabilityCheck(void *Unused) {
  (void)Unused;
  _initializeAvailabilityCheck(/*LoadPlist=*/true);
}

static void initializeAvailabilityCheck(void *Unused) {
  (void)Unused;
  _initializeAvailabilityCheck(/*LoadPlist=*/false);
}

// This old API entry point is no longer used by Clang for Darwin. We still need
// to keep it around to ensure that object files that reference it are still
// usable when linked with new compiler-rt.
int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) {
  // Populate the global version variables, if they haven't already.
  dispatch_once_f(&CompatibilityDispatchOnceCounter, NULL,
                  compatibilityInitializeAvailabilityCheck);

  if (Major < GlobalMajor)
    return 1;
  if (Major > GlobalMajor)
    return 0;
  if (Minor < GlobalMinor)
    return 1;
  if (Minor > GlobalMinor)
    return 0;
  return Subminor <= GlobalSubminor;
}

static inline uint32_t ConstructVersion(uint32_t Major, uint32_t Minor,
                                        uint32_t Subminor) {
  return ((Major & 0xffff) << 16) | ((Minor & 0xff) << 8) | (Subminor & 0xff);
}

#define PLATFORM_MACOS 1

int32_t __isPlatformVersionAtLeast(uint32_t Platform, uint32_t Major,
                                   uint32_t Minor, uint32_t Subminor) {
  dispatch_once_f(&DispatchOnceCounter, NULL, initializeAvailabilityCheck);

  if (!AvailabilityVersionCheck) {
    return __isOSVersionAtLeast(Major, Minor, Subminor);
  }
  dyld_build_version_t Versions[] = {
      {Platform, ConstructVersion(Major, Minor, Subminor)}};
  return AvailabilityVersionCheck(1, Versions);
}

#if TARGET_OS_OSX

int32_t __isPlatformOrVariantPlatformVersionAtLeast(
    uint32_t Platform, uint32_t Major, uint32_t Minor, uint32_t Subminor,
    uint32_t Platform2, uint32_t Major2, uint32_t Minor2, uint32_t Subminor2) {
  dispatch_once_f(&DispatchOnceCounter, NULL, initializeAvailabilityCheck);

  if (!AvailabilityVersionCheck) {
    // Handle case of back-deployment for older macOS.
    if (Platform == PLATFORM_MACOS) {
      return __isOSVersionAtLeast(Major, Minor, Subminor);
    }
    assert(Platform2 == PLATFORM_MACOS && "unexpected platform");
    return __isOSVersionAtLeast(Major2, Minor2, Subminor2);
  }
  dyld_build_version_t Versions[] = {
      {Platform, ConstructVersion(Major, Minor, Subminor)},
      {Platform2, ConstructVersion(Major2, Minor2, Subminor2)}};
  return AvailabilityVersionCheck(2, Versions);
}

#endif

#elif __ANDROID__

#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <sys/system_properties.h>

static int SdkVersion;
static int IsPreRelease;

static void readSystemProperties(void) {
  char buf[PROP_VALUE_MAX];

  if (__system_property_get("ro.build.version.sdk", buf) == 0) {
    // When the system property doesn't exist, defaults to future API level.
    SdkVersion = __ANDROID_API_FUTURE__;
  } else {
    SdkVersion = atoi(buf);
  }

  if (__system_property_get("ro.build.version.codename", buf) == 0) {
    IsPreRelease = 1;
  } else {
    IsPreRelease = strcmp(buf, "REL") != 0;
  }
  return;
}

int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) {
  (void) Minor;
  (void) Subminor;
  static pthread_once_t once = PTHREAD_ONCE_INIT;
  pthread_once(&once, readSystemProperties);

  // Allow all on pre-release. Note that we still rely on compile-time checks.
  return SdkVersion >= Major || IsPreRelease;
}

#else

// Silence an empty translation unit warning.
typedef int unused;

#endif
