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 <dispatch/dispatch.h>
18#include <dlfcn.h>
19#include <stdint.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23
24// These three variables hold the host's OS version.
25static int32_t GlobalMajor, GlobalMinor, GlobalSubminor;
26static dispatch_once_t DispatchOnceCounter;
27
28// We can't include <CoreFoundation/CoreFoundation.h> directly from here, so
29// just forward declare everything that we need from it.
30
31typedef const void *CFDataRef, *CFAllocatorRef, *CFPropertyListRef,
32    *CFStringRef, *CFDictionaryRef, *CFTypeRef, *CFErrorRef;
33
34#if __LLP64__
35typedef unsigned long long CFTypeID;
36typedef unsigned long long CFOptionFlags;
37typedef signed long long CFIndex;
38#else
39typedef unsigned long CFTypeID;
40typedef unsigned long CFOptionFlags;
41typedef signed long CFIndex;
42#endif
43
44typedef unsigned char UInt8;
45typedef _Bool Boolean;
46typedef CFIndex CFPropertyListFormat;
47typedef uint32_t CFStringEncoding;
48
49// kCFStringEncodingASCII analog.
50#define CF_STRING_ENCODING_ASCII 0x0600
51// kCFStringEncodingUTF8 analog.
52#define CF_STRING_ENCODING_UTF8 0x08000100
53#define CF_PROPERTY_LIST_IMMUTABLE 0
54
55typedef CFDataRef (*CFDataCreateWithBytesNoCopyFuncTy)(CFAllocatorRef,
56                                                       const UInt8 *, CFIndex,
57                                                       CFAllocatorRef);
58typedef CFPropertyListRef (*CFPropertyListCreateWithDataFuncTy)(
59    CFAllocatorRef, CFDataRef, CFOptionFlags, CFPropertyListFormat *,
60    CFErrorRef *);
61typedef CFPropertyListRef (*CFPropertyListCreateFromXMLDataFuncTy)(
62    CFAllocatorRef, CFDataRef, CFOptionFlags, CFStringRef *);
63typedef CFStringRef (*CFStringCreateWithCStringNoCopyFuncTy)(CFAllocatorRef,
64                                                             const char *,
65                                                             CFStringEncoding,
66                                                             CFAllocatorRef);
67typedef const void *(*CFDictionaryGetValueFuncTy)(CFDictionaryRef,
68                                                  const void *);
69typedef CFTypeID (*CFGetTypeIDFuncTy)(CFTypeRef);
70typedef CFTypeID (*CFStringGetTypeIDFuncTy)(void);
71typedef Boolean (*CFStringGetCStringFuncTy)(CFStringRef, char *, CFIndex,
72                                            CFStringEncoding);
73typedef void (*CFReleaseFuncTy)(CFTypeRef);
74
75// Find and parse the SystemVersion.plist file.
76static void parseSystemVersionPList(void *Unused) {
77  (void)Unused;
78  // Load CoreFoundation dynamically
79  const void *NullAllocator = dlsym(RTLD_DEFAULT, "kCFAllocatorNull");
80  if (!NullAllocator)
81    return;
82  const CFAllocatorRef AllocatorNull = *(const CFAllocatorRef *)NullAllocator;
83  CFDataCreateWithBytesNoCopyFuncTy CFDataCreateWithBytesNoCopyFunc =
84      (CFDataCreateWithBytesNoCopyFuncTy)dlsym(RTLD_DEFAULT,
85                                               "CFDataCreateWithBytesNoCopy");
86  if (!CFDataCreateWithBytesNoCopyFunc)
87    return;
88  CFPropertyListCreateWithDataFuncTy CFPropertyListCreateWithDataFunc =
89      (CFPropertyListCreateWithDataFuncTy)dlsym(RTLD_DEFAULT,
90                                                "CFPropertyListCreateWithData");
91// CFPropertyListCreateWithData was introduced only in macOS 10.6+, so it
92// will be NULL on earlier OS versions.
93#pragma clang diagnostic push
94#pragma clang diagnostic ignored "-Wdeprecated-declarations"
95  CFPropertyListCreateFromXMLDataFuncTy CFPropertyListCreateFromXMLDataFunc =
96      (CFPropertyListCreateFromXMLDataFuncTy)dlsym(
97          RTLD_DEFAULT, "CFPropertyListCreateFromXMLData");
98#pragma clang diagnostic pop
99  // CFPropertyListCreateFromXMLDataFunc is deprecated in macOS 10.10, so it
100  // might be NULL in future OS versions.
101  if (!CFPropertyListCreateWithDataFunc && !CFPropertyListCreateFromXMLDataFunc)
102    return;
103  CFStringCreateWithCStringNoCopyFuncTy CFStringCreateWithCStringNoCopyFunc =
104      (CFStringCreateWithCStringNoCopyFuncTy)dlsym(
105          RTLD_DEFAULT, "CFStringCreateWithCStringNoCopy");
106  if (!CFStringCreateWithCStringNoCopyFunc)
107    return;
108  CFDictionaryGetValueFuncTy CFDictionaryGetValueFunc =
109      (CFDictionaryGetValueFuncTy)dlsym(RTLD_DEFAULT, "CFDictionaryGetValue");
110  if (!CFDictionaryGetValueFunc)
111    return;
112  CFGetTypeIDFuncTy CFGetTypeIDFunc =
113      (CFGetTypeIDFuncTy)dlsym(RTLD_DEFAULT, "CFGetTypeID");
114  if (!CFGetTypeIDFunc)
115    return;
116  CFStringGetTypeIDFuncTy CFStringGetTypeIDFunc =
117      (CFStringGetTypeIDFuncTy)dlsym(RTLD_DEFAULT, "CFStringGetTypeID");
118  if (!CFStringGetTypeIDFunc)
119    return;
120  CFStringGetCStringFuncTy CFStringGetCStringFunc =
121      (CFStringGetCStringFuncTy)dlsym(RTLD_DEFAULT, "CFStringGetCString");
122  if (!CFStringGetCStringFunc)
123    return;
124  CFReleaseFuncTy CFReleaseFunc =
125      (CFReleaseFuncTy)dlsym(RTLD_DEFAULT, "CFRelease");
126  if (!CFReleaseFunc)
127    return;
128
129  char *PListPath = "/System/Library/CoreServices/SystemVersion.plist";
130
131#if TARGET_OS_SIMULATOR
132  char *PListPathPrefix = getenv("IPHONE_SIMULATOR_ROOT");
133  if (!PListPathPrefix)
134    return;
135  char FullPath[strlen(PListPathPrefix) + strlen(PListPath) + 1];
136  strcpy(FullPath, PListPathPrefix);
137  strcat(FullPath, PListPath);
138  PListPath = FullPath;
139#endif
140  FILE *PropertyList = fopen(PListPath, "r");
141  if (!PropertyList)
142    return;
143
144  // Dynamically allocated stuff.
145  CFDictionaryRef PListRef = NULL;
146  CFDataRef FileContentsRef = NULL;
147  UInt8 *PListBuf = NULL;
148
149  fseek(PropertyList, 0, SEEK_END);
150  long PListFileSize = ftell(PropertyList);
151  if (PListFileSize < 0)
152    goto Fail;
153  rewind(PropertyList);
154
155  PListBuf = malloc((size_t)PListFileSize);
156  if (!PListBuf)
157    goto Fail;
158
159  size_t NumRead = fread(PListBuf, 1, (size_t)PListFileSize, PropertyList);
160  if (NumRead != (size_t)PListFileSize)
161    goto Fail;
162
163  // Get the file buffer into CF's format. We pass in a null allocator here *
164  // because we free PListBuf ourselves
165  FileContentsRef = (*CFDataCreateWithBytesNoCopyFunc)(
166      NULL, PListBuf, (CFIndex)NumRead, AllocatorNull);
167  if (!FileContentsRef)
168    goto Fail;
169
170  if (CFPropertyListCreateWithDataFunc)
171    PListRef = (*CFPropertyListCreateWithDataFunc)(
172        NULL, FileContentsRef, CF_PROPERTY_LIST_IMMUTABLE, NULL, NULL);
173  else
174    PListRef = (*CFPropertyListCreateFromXMLDataFunc)(
175        NULL, FileContentsRef, CF_PROPERTY_LIST_IMMUTABLE, NULL);
176  if (!PListRef)
177    goto Fail;
178
179  CFStringRef ProductVersion = (*CFStringCreateWithCStringNoCopyFunc)(
180      NULL, "ProductVersion", CF_STRING_ENCODING_ASCII, AllocatorNull);
181  if (!ProductVersion)
182    goto Fail;
183  CFTypeRef OpaqueValue = (*CFDictionaryGetValueFunc)(PListRef, ProductVersion);
184  (*CFReleaseFunc)(ProductVersion);
185  if (!OpaqueValue ||
186      (*CFGetTypeIDFunc)(OpaqueValue) != (*CFStringGetTypeIDFunc)())
187    goto Fail;
188
189  char VersionStr[32];
190  if (!(*CFStringGetCStringFunc)((CFStringRef)OpaqueValue, VersionStr,
191                                 sizeof(VersionStr), CF_STRING_ENCODING_UTF8))
192    goto Fail;
193  sscanf(VersionStr, "%d.%d.%d", &GlobalMajor, &GlobalMinor, &GlobalSubminor);
194
195Fail:
196  if (PListRef)
197    (*CFReleaseFunc)(PListRef);
198  if (FileContentsRef)
199    (*CFReleaseFunc)(FileContentsRef);
200  free(PListBuf);
201  fclose(PropertyList);
202}
203
204int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) {
205  // Populate the global version variables, if they haven't already.
206  dispatch_once_f(&DispatchOnceCounter, NULL, parseSystemVersionPList);
207
208  if (Major < GlobalMajor)
209    return 1;
210  if (Major > GlobalMajor)
211    return 0;
212  if (Minor < GlobalMinor)
213    return 1;
214  if (Minor > GlobalMinor)
215    return 0;
216  return Subminor <= GlobalSubminor;
217}
218
219#else
220
221// Silence an empty translation unit warning.
222typedef int unused;
223
224#endif
225