+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include <sys/conf.h>
+#include <sys/vnode.h>
+#include <sys/vnode_internal.h>
+#include <sys/fcntl.h>
+#include <IOKit/IOPolledInterface.h>
+#include <IOKit/IOBufferMemoryDescriptor.h>
+
+IOPolledFileIOVars * gIOPolledCoreFileVars;
+kern_return_t gIOPolledCoreFileOpenRet = kIOReturnNotReady;
+IOPolledCoreFileMode_t gIOPolledCoreFileMode = kIOPolledCoreFileModeNotInitialized;
+
+#if IOPOLLED_COREFILE
+
+#if defined(XNU_TARGET_OS_BRIDGE)
+// On bridgeOS allocate a 150MB corefile and leave 150MB free
+#define kIOCoreDumpSize 150ULL*1024ULL*1024ULL
+#define kIOCoreDumpFreeSize 150ULL*1024ULL*1024ULL
+
+#elif !defined(XNU_TARGET_OS_OSX) /* defined(XNU_TARGET_OS_BRIDGE) */
+// On embedded devices with >3GB DRAM we allocate a 500MB corefile
+// otherwise allocate a 350MB corefile. Leave 350 MB free
+
+#define kIOCoreDumpMinSize 350ULL*1024ULL*1024ULL
+#define kIOCoreDumpLargeSize 500ULL*1024ULL*1024ULL
+
+#define kIOCoreDumpFreeSize 350ULL*1024ULL*1024ULL
+
+#else /* defined(XNU_TARGET_OS_BRIDGE) */
+// on macOS devices allocate a corefile sized at 1GB / 32GB of DRAM,
+// fallback to a 1GB corefile and leave at least 1GB free
+#define kIOCoreDumpMinSize 1024ULL*1024ULL*1024ULL
+#define kIOCoreDumpIncrementalSize 1024ULL*1024ULL*1024ULL
+
+#define kIOCoreDumpFreeSize 1024ULL*1024ULL*1024ULL
+
+// on older macOS devices we allocate a 1MB file at boot
+// to store a panic time stackshot
+#define kIOStackshotFileSize 1024ULL*1024ULL
+
+#endif /* defined(XNU_TARGET_OS_BRIDGE) */
+
+static IOPolledCoreFileMode_t
+GetCoreFileMode()
+{
+ if (on_device_corefile_enabled()) {
+ return kIOPolledCoreFileModeCoredump;
+ } else if (panic_stackshot_to_disk_enabled()) {
+ return kIOPolledCoreFileModeStackshot;
+ } else {
+ return kIOPolledCoreFileModeDisabled;
+ }
+}
+
+static void
+IOCoreFileGetSize(uint64_t *ideal_size, uint64_t *fallback_size, uint64_t *free_space_to_leave, IOPolledCoreFileMode_t mode)
+{
+ unsigned int requested_corefile_size = 0;
+
+ *ideal_size = *fallback_size = *free_space_to_leave = 0;
+
+#if defined(XNU_TARGET_OS_BRIDGE)
+#pragma unused(mode)
+ *ideal_size = *fallback_size = kIOCoreDumpSize;
+ *free_space_to_leave = kIOCoreDumpFreeSize;
+#elif !defined(XNU_TARGET_OS_OSX) /* defined(XNU_TARGET_OS_BRIDGE) */
+#pragma unused(mode)
+ *ideal_size = *fallback_size = kIOCoreDumpMinSize;
+
+ if (max_mem > (3 * 1024ULL * 1024ULL * 1024ULL)) {
+ *ideal_size = kIOCoreDumpLargeSize;
+ }
+
+ *free_space_to_leave = kIOCoreDumpFreeSize;
+#else /* defined(XNU_TARGET_OS_BRIDGE) */
+ if (mode == kIOPolledCoreFileModeCoredump) {
+ *ideal_size = *fallback_size = kIOCoreDumpMinSize;
+ if (kIOCoreDumpIncrementalSize != 0 && max_mem > (32 * 1024ULL * 1024ULL * 1024ULL)) {
+ *ideal_size = ((ROUNDUP(max_mem, (32 * 1024ULL * 1024ULL * 1024ULL)) / (32 * 1024ULL * 1024ULL * 1024ULL)) * kIOCoreDumpIncrementalSize);
+ }
+ *free_space_to_leave = kIOCoreDumpFreeSize;
+ } else if (mode == kIOPolledCoreFileModeStackshot) {
+ *ideal_size = *fallback_size = *free_space_to_leave = kIOStackshotFileSize;
+ }
+#endif /* defined(XNU_TARGET_OS_BRIDGE) */
+ // If a custom size was requested, override the ideal and requested sizes
+ if (PE_parse_boot_argn("corefile_size_mb", &requested_corefile_size, sizeof(requested_corefile_size))) {
+ IOLog("Boot-args specify %d MB kernel corefile\n", requested_corefile_size);
+
+ *ideal_size = *fallback_size = (requested_corefile_size * 1024ULL * 1024ULL);
+ }
+
+ return;
+}
+
+static void
+IOOpenPolledCoreFile(thread_call_param_t __unused, thread_call_param_t corefilename)
+{
+ assert(corefilename != NULL);
+
+ IOReturn err;
+ char *filename = (char *) corefilename;
+ uint64_t corefile_size_bytes = 0, corefile_fallback_size_bytes = 0, free_space_to_leave_bytes = 0;
+ IOPolledCoreFileMode_t mode_to_init = GetCoreFileMode();
+
+ if (gIOPolledCoreFileVars) {
+ return;
+ }
+ if (!IOPolledInterface::gMetaClass.getInstanceCount()) {
+ return;
+ }
+
+ if (mode_to_init == kIOPolledCoreFileModeDisabled) {
+ gIOPolledCoreFileMode = kIOPolledCoreFileModeDisabled;
+ return;
+ }
+
+ // We'll overwrite this once we open the file, we update this to mark that we have made
+ // it past initialization
+ gIOPolledCoreFileMode = kIOPolledCoreFileModeClosed;
+
+ IOCoreFileGetSize(&corefile_size_bytes, &corefile_fallback_size_bytes, &free_space_to_leave_bytes, mode_to_init);
+
+ do {
+ err = IOPolledFileOpen(filename, kIOPolledFileCreate, corefile_size_bytes, free_space_to_leave_bytes,
+ NULL, 0, &gIOPolledCoreFileVars, NULL, NULL, NULL);
+ if (kIOReturnSuccess == err) {
+ break;
+ } else if (kIOReturnNoSpace == err) {
+ IOLog("Failed to open corefile of size %llu MB (low disk space)",
+ (corefile_size_bytes / (1024ULL * 1024ULL)));
+ if (corefile_size_bytes == corefile_fallback_size_bytes) {
+ gIOPolledCoreFileOpenRet = err;
+ return;
+ }
+ } else {
+ IOLog("Failed to open corefile of size %llu MB (returned error 0x%x)\n",
+ (corefile_size_bytes / (1024ULL * 1024ULL)), err);
+ gIOPolledCoreFileOpenRet = err;
+ return;
+ }
+
+ err = IOPolledFileOpen(filename, kIOPolledFileCreate, corefile_fallback_size_bytes, free_space_to_leave_bytes,
+ NULL, 0, &gIOPolledCoreFileVars, NULL, NULL, NULL);
+ if (kIOReturnSuccess != err) {
+ IOLog("Failed to open corefile of size %llu MB (returned error 0x%x)\n",
+ (corefile_fallback_size_bytes / (1024ULL * 1024ULL)), err);
+ gIOPolledCoreFileOpenRet = err;
+ return;
+ }
+ } while (false);
+
+ gIOPolledCoreFileOpenRet = IOPolledFilePollersSetup(gIOPolledCoreFileVars, kIOPolledPreflightCoreDumpState);
+ if (kIOReturnSuccess != gIOPolledCoreFileOpenRet) {
+ IOPolledFileClose(&gIOPolledCoreFileVars, 0, NULL, 0, 0, 0);
+ IOLog("IOPolledFilePollersSetup for corefile failed with error: 0x%x\n", err);
+ } else {
+ IOLog("Opened corefile of size %llu MB\n", (corefile_size_bytes / (1024ULL * 1024ULL)));
+ gIOPolledCoreFileMode = mode_to_init;
+ }
+
+ return;
+}
+
+static void
+IOClosePolledCoreFile(void)
+{
+ gIOPolledCoreFileOpenRet = kIOReturnNotOpen;
+ gIOPolledCoreFileMode = kIOPolledCoreFileModeClosed;
+ IOPolledFilePollersClose(gIOPolledCoreFileVars, kIOPolledPostflightCoreDumpState);
+ IOPolledFileClose(&gIOPolledCoreFileVars, 0, NULL, 0, 0, 0);
+}
+
+#endif /* IOPOLLED_COREFILE */
+
+extern "C" void
+IOBSDMountChange(struct mount * mp, uint32_t op)
+{
+#if IOPOLLED_COREFILE
+ uint64_t flags;
+ char path[128];
+ int pathLen;
+ vnode_t vn;
+ int result;
+
+ switch (op) {
+ case kIOMountChangeMount:
+ case kIOMountChangeDidResize:
+
+ if (gIOPolledCoreFileVars) {
+ break;
+ }
+ flags = vfs_flags(mp);
+ if (MNT_RDONLY & flags) {
+ break;
+ }
+ if (!(MNT_LOCAL & flags)) {
+ break;
+ }
+
+ vn = vfs_vnodecovered(mp);
+ if (!vn) {
+ break;
+ }
+ pathLen = sizeof(path);
+ result = vn_getpath(vn, &path[0], &pathLen);
+ vnode_put(vn);
+ if (0 != result) {
+ break;
+ }
+ if (!pathLen) {
+ break;
+ }
+#if defined(XNU_TARGET_OS_BRIDGE)
+ // on bridgeOS systems we put the core in /private/var/internal. We don't
+ // want to match with /private/var because /private/var/internal is often mounted
+ // over /private/var
+ if ((pathLen - 1) < (int) strlen("/private/var/internal")) {
+ break;
+ }
+#endif
+ if (0 != strncmp(path, kIOCoreDumpPath, pathLen - 1)) {
+ break;
+ }
+
+ thread_call_enter1(corefile_open_call, (void *) kIOCoreDumpPath);
+ break;
+
+ case kIOMountChangeUnmount:
+ case kIOMountChangeWillResize:
+ if (gIOPolledCoreFileVars && (mp == kern_file_mount(gIOPolledCoreFileVars->fileRef))) {
+ thread_call_cancel_wait(corefile_open_call);
+ IOClosePolledCoreFile();
+ }
+ break;
+ }
+#endif /* IOPOLLED_COREFILE */
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+extern "C" boolean_t
+IOTaskHasEntitlement(task_t task, const char * entitlement)
+{
+ OSObject * obj;
+ obj = IOUserClient::copyClientEntitlement(task, entitlement);
+ if (!obj) {
+ return false;
+ }
+ obj->release();
+ return obj != kOSBooleanFalse;
+}
+
+extern "C" boolean_t
+IOVnodeHasEntitlement(vnode_t vnode, int64_t off, const char *entitlement)
+{
+ OSObject * obj;
+ off_t offset = (off_t)off;
+
+ obj = IOUserClient::copyClientEntitlementVnode(vnode, offset, entitlement);
+ if (!obj) {
+ return false;
+ }
+ obj->release();
+ return obj != kOSBooleanFalse;
+}
+
+extern "C" char *
+IOVnodeGetEntitlement(vnode_t vnode, int64_t off, const char *entitlement)
+{
+ OSObject *obj = NULL;
+ OSString *str = NULL;
+ size_t len;
+ char *value = NULL;
+ off_t offset = (off_t)off;
+
+ obj = IOUserClient::copyClientEntitlementVnode(vnode, offset, entitlement);
+ if (obj != NULL) {
+ str = OSDynamicCast(OSString, obj);
+ if (str != NULL) {
+ len = str->getLength() + 1;
+ value = (char *)kheap_alloc(KHEAP_DATA_BUFFERS, len, Z_WAITOK);
+ strlcpy(value, str->getCStringNoCopy(), len);
+ }
+ obj->release();
+ }
+ return value;
+}