mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
3607 lines
114 KiB
Diff
3607 lines
114 KiB
Diff
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
index 84161fd..5b23944 100644
|
|
--- a/CMakeLists.txt
|
|
+++ b/CMakeLists.txt
|
|
@@ -17,6 +17,8 @@ include_directories(
|
|
${X11_Xrandr_INCLUDE_PATH}
|
|
)
|
|
include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR})
|
|
+# IOPMrootDomain.mm loads the host's libsystemd through the ELF loader.
|
|
+include_directories(${CMAKE_SOURCE_DIR}/src/startup/mldr/elfcalls)
|
|
|
|
mig(iokitmig.defs)
|
|
mig(powermanagement.defs)
|
|
@@ -36,7 +38,13 @@ set(iokitd_sources
|
|
src/IODisplayConnect.mm
|
|
src/IODisplayConnectX11.mm
|
|
src/PowerAssertions.mm
|
|
+ src/PowerSources.mm
|
|
src/PowerAssertionsX11.mm
|
|
+ src/IOPlatformExpertDevice.mm
|
|
+ src/IOPMrootDomain.mm
|
|
+ src/IOInterestNotification.mm
|
|
+ src/LogindSleep.c
|
|
+ src/HostPowerInhibit.c
|
|
src/IOSurfaceRoot.mm
|
|
src/IOSurface.mm
|
|
src/IOUserClient.mm
|
|
diff --git a/src/IORegistryEntry.mm b/src/IORegistryEntry.mm
|
|
index 50c10fe..7026e1c 100644
|
|
--- a/src/IORegistryEntry.mm
|
|
+++ b/src/IORegistryEntry.mm
|
|
@@ -1,11 +1,15 @@
|
|
#include "IORegistryEntry.h"
|
|
#include <stack>
|
|
#include <IOKit/IOCFSerialize.h>
|
|
+#include <CoreFoundation/CFNumber.h>
|
|
#include <cstring>
|
|
#include <vector>
|
|
#include <IOKit/IOKitLib.h>
|
|
#include <IOKit/IOReturn.h>
|
|
#include <sstream>
|
|
+#include <limits>
|
|
+#import <Foundation/NSData.h>
|
|
+#import <Foundation/NSNumber.h>
|
|
#include "IOIterator.h"
|
|
extern "C" {
|
|
#include "iokitmigServer.h"
|
|
@@ -29,6 +33,12 @@ IORegistryEntry::~IORegistryEntry()
|
|
{
|
|
std::unique_lock l(m_entriesByIdMutex);
|
|
m_entriesById.erase(m_id);
|
|
+ // Registry requests and object teardown run on iokitd's main dispatch queue.
|
|
+ // Detach both directions so a later traversal cannot follow a deleted entry.
|
|
+ for (auto& plane : m_parents)
|
|
+ for (auto* parent : plane.second) parent->m_children[plane.first].erase(this);
|
|
+ for (auto& plane : m_children)
|
|
+ for (auto* child : plane.second) child->m_parents[plane.first].erase(this);
|
|
}
|
|
|
|
IORegistryEntry* IORegistryEntry::findById(uint64_t id)
|
|
@@ -79,29 +89,42 @@ std::set<IORegistryEntry*> IORegistryEntry::getChildren(const char* plane)
|
|
|
|
void IORegistryEntry::registerInPlane(const char* plane, const char* pathName, IORegistryEntry* parent)
|
|
{
|
|
+ if (!plane || !*plane || !pathName || !*pathName || !parent || parent == this) return;
|
|
+ std::set<IORegistryEntry*> visited;
|
|
+ std::vector<IORegistryEntry*> pending { parent };
|
|
+ while (!pending.empty()) {
|
|
+ auto* current = pending.back(); pending.pop_back();
|
|
+ if (current == this) return; // refuse cycles, including indirect ones
|
|
+ if (!visited.insert(current).second) continue;
|
|
+ for (auto* ancestor : current->getParents(plane)) pending.push_back(ancestor);
|
|
+ }
|
|
m_name[plane] = pathName;
|
|
+ if (parent == root()) parent->m_name.emplace(plane, "Root");
|
|
|
|
auto it = m_parents.find(plane);
|
|
|
|
if (it == m_parents.end())
|
|
- it = m_parents.insert(std::make_pair(std::string_view(plane), std::set<IORegistryEntry*>())).first;
|
|
+ it = m_parents.insert(std::make_pair(std::string(plane), std::set<IORegistryEntry*>())).first;
|
|
|
|
it->second.insert(parent);
|
|
|
|
it = parent->m_children.find(plane);
|
|
if (it == parent->m_children.end())
|
|
- it = parent->m_children.insert(std::make_pair(std::string_view(plane), std::set<IORegistryEntry*>())).first;
|
|
+ it = parent->m_children.insert(std::make_pair(std::string(plane), std::set<IORegistryEntry*>())).first;
|
|
|
|
it->second.insert(this);
|
|
}
|
|
|
|
std::string IORegistryEntry::getPath(const char* plane)
|
|
{
|
|
+ if (!inPlane(plane)) return "?unsup_plane?";
|
|
std::stack<std::string> components;
|
|
+ std::set<IORegistryEntry*> visited;
|
|
IORegistryEntry* cur = this;
|
|
|
|
while (true)
|
|
{
|
|
+ if (!visited.insert(cur).second) return "?unsup_plane?";
|
|
std::set<IORegistryEntry*> parents = cur->getParents(plane);
|
|
if (parents.empty())
|
|
{
|
|
@@ -113,18 +136,22 @@ std::string IORegistryEntry::getPath(const char* plane)
|
|
|
|
components.push(cur->getName(plane));
|
|
|
|
- IORegistryEntry* parent = *parents.begin();
|
|
+ IORegistryEntry* parent = nullptr;
|
|
+ for (auto* candidate : parents) if (candidate->inPlane(plane)) { parent = candidate; break; }
|
|
+ if (!parent) return "?unsup_plane?";
|
|
cur = parent;
|
|
}
|
|
|
|
std::string path = plane;
|
|
path += ":";
|
|
+ if (components.empty()) path += "/";
|
|
|
|
while (!components.empty())
|
|
{
|
|
path += "/";
|
|
path += components.top();
|
|
components.pop();
|
|
+ if (path.size() > 65535) return "?unsup_plane?";
|
|
}
|
|
|
|
return path;
|
|
@@ -166,11 +193,15 @@ kern_return_t is_io_registry_entry_get_path
|
|
io_string_t path
|
|
)
|
|
{
|
|
+ if (!path) return kIOReturnBadArgument;
|
|
IORegistryEntry* e = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
|
if (!e)
|
|
return kIOReturnBadArgument;
|
|
|
|
+ if (!plane || !memchr(plane, 0, sizeof(io_name_t)) || !e->inPlane(plane)) return kIOReturnBadArgument;
|
|
std::string mypath = e->getPath(plane);
|
|
+ if (mypath == "?unsup_plane?") return kIOReturnBadArgument;
|
|
+ if (mypath.size() >= sizeof(io_string_t)) { path[0] = 0; return kIOReturnNoSpace; }
|
|
strlcpy(path, mypath.c_str(), sizeof(io_string_t));
|
|
|
|
return kIOReturnSuccess;
|
|
@@ -386,55 +417,59 @@ kern_return_t is_io_registry_entry_get_location_in_plane
|
|
return KERN_NOT_SUPPORTED;
|
|
}
|
|
|
|
-kern_return_t is_io_registry_entry_from_path
|
|
-(
|
|
- mach_port_t master_port,
|
|
- io_string_t path,
|
|
- mach_port_t *registry_entry
|
|
-)
|
|
+bool IORegistryEntry::inPlane(const char* plane) const
|
|
{
|
|
- *registry_entry = 0;
|
|
-
|
|
- std::vector<std::string> components;
|
|
- std::istringstream iss(path);
|
|
-
|
|
- for (std::string token; std::getline(iss, token, '/'); )
|
|
- {
|
|
- if (!token.empty())
|
|
- components.push_back(std::move(token));
|
|
+ if (!plane || !*plane || m_name.find(plane) == m_name.end()) return false;
|
|
+ std::set<const IORegistryEntry*> visited;
|
|
+ std::vector<const IORegistryEntry*> pending { this };
|
|
+ while (!pending.empty()) {
|
|
+ auto* current = pending.back(); pending.pop_back();
|
|
+ if (current == root()) return true;
|
|
+ if (!visited.insert(current).second) continue;
|
|
+ auto parents = current->m_parents.find(plane);
|
|
+ if (parents != current->m_parents.end())
|
|
+ for (auto* parent : parents->second) pending.push_back(parent);
|
|
}
|
|
-
|
|
- if (components.empty())
|
|
- return kIOReturnBadArgument;
|
|
-
|
|
- std::string& planeName = components[0];
|
|
- if (planeName[planeName.length()-1] != ':')
|
|
- return kIOReturnBadArgument;
|
|
- else
|
|
- planeName.resize(planeName.length()-1); // remove the final colon
|
|
-
|
|
- IORegistryEntry* current = IORegistryEntry::root();
|
|
- for (size_t i = 1; i < components.size(); i++)
|
|
- {
|
|
- auto children = current->getChildren(planeName.c_str());
|
|
- IORegistryEntry* next = nullptr;
|
|
+ return false;
|
|
+}
|
|
|
|
- for (IORegistryEntry* e : children)
|
|
- {
|
|
- if (strcasecmp(e->getName(planeName.c_str()).c_str(), components[i].c_str()) == 0)
|
|
- {
|
|
- next = e;
|
|
- break;
|
|
- }
|
|
+IORegistryEntry* IORegistryEntry::fromPath(std::string_view path)
|
|
+{
|
|
+ if (path.empty() || path.size() > 65535 || path.find('\0') != std::string_view::npos) return nullptr;
|
|
+ auto colon = path.find(':');
|
|
+ if (colon == 0 || colon == std::string_view::npos || colon >= sizeof(io_name_t)) return nullptr;
|
|
+ std::string plane(path.substr(0, colon));
|
|
+ if (plane.find('/') != std::string::npos) return nullptr;
|
|
+ auto* current = root();
|
|
+ if (!current->inPlane(plane.c_str())) return nullptr;
|
|
+ path.remove_prefix(colon + 1);
|
|
+ if (!path.empty() && path.front() != '/') return nullptr;
|
|
+ while (!path.empty()) {
|
|
+ path.remove_prefix(1);
|
|
+ auto slash = path.find('/');
|
|
+ auto name = path.substr(0, slash);
|
|
+ if (!name.empty()) {
|
|
+ if (name == "." || name == "..") return nullptr;
|
|
+ IORegistryEntry* next = nullptr;
|
|
+ for (auto* child : current->getChildren(plane.c_str()))
|
|
+ if (child->getName(plane.c_str()) == name) { next = child; break; }
|
|
+ if (!next) return nullptr;
|
|
+ current = next;
|
|
}
|
|
-
|
|
- if (!next)
|
|
- return kIOReturnNoDevice;
|
|
- current = next;
|
|
+ if (slash == std::string_view::npos) break;
|
|
+ path.remove_prefix(slash);
|
|
}
|
|
+ return current; // borrowed until this main-queue request completes
|
|
+}
|
|
|
|
- *registry_entry = current->port();
|
|
-
|
|
+kern_return_t is_io_registry_entry_from_path(mach_port_t master_port, io_string_t path, mach_port_t* registry_entry)
|
|
+{
|
|
+ if (!registry_entry) return kIOReturnBadArgument;
|
|
+ *registry_entry = MACH_PORT_NULL;
|
|
+ if (!path || !memchr(path, 0, sizeof(io_string_t))) return kIOReturnBadArgument;
|
|
+ auto* entry = IORegistryEntry::fromPath(path);
|
|
+ if (!entry) return kIOReturnNoDevice;
|
|
+ *registry_entry = entry->port();
|
|
return kIOReturnSuccess;
|
|
}
|
|
|
|
@@ -513,3 +548,189 @@ kern_return_t is_io_registry_entry_get_property_bin
|
|
return kr;
|
|
}
|
|
|
|
+
|
|
+kern_return_t is_io_registry_entry_get_property_bin_buf
|
|
+(
|
|
+ mach_port_t registry_entry,
|
|
+ io_name_t plane,
|
|
+ io_name_t property_name,
|
|
+ uint32_t options,
|
|
+ mach_vm_address_t buf,
|
|
+ mach_vm_size_t *bufsize,
|
|
+ io_buf_ptr_t *properties,
|
|
+ mach_msg_type_number_t *propertiesCnt
|
|
+)
|
|
+{
|
|
+ IORegistryEntry* e = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
|
+ if (!e)
|
|
+ return kIOReturnBadArgument;
|
|
+
|
|
+ *propertiesCnt = 0;
|
|
+ *properties = nullptr;
|
|
+
|
|
+ NSString* propName = [NSString stringWithUTF8String: property_name];
|
|
+ id value = nil;
|
|
+
|
|
+ if ((options & kIORegistryIterateRecursively) && plane[0])
|
|
+ value = [findKey(e, plane, propName) objectForKey: propName];
|
|
+ else
|
|
+ value = [e->getProperties() objectForKey: propName];
|
|
+
|
|
+ if (!value)
|
|
+ return kIOReturnNotFound;
|
|
+
|
|
+ // Unlike io_registry_entry_get_property_bin, the reply carries the value itself.
|
|
+ // The caller's buffer lives in another task, so always answer out-of-line, which the
|
|
+ // protocol allows when the result does not go into 'buf'.
|
|
+ CFDataRef data = IOCFSerialize((CFTypeRef) value, kIOCFSerializeToBinary);
|
|
+ if (!data)
|
|
+ return kIOReturnNoMemory;
|
|
+
|
|
+ *propertiesCnt = CFDataGetLength(data);
|
|
+ kern_return_t kr = vm_allocate(mach_task_self(), (vm_address_t*) properties, *propertiesCnt, true);
|
|
+
|
|
+ if (kr == kIOReturnSuccess)
|
|
+ memcpy(*properties, CFDataGetBytePtr(data), *propertiesCnt);
|
|
+ else
|
|
+ *propertiesCnt = 0;
|
|
+
|
|
+ CFRelease(data);
|
|
+ return kr;
|
|
+}
|
|
+
|
|
+kern_return_t is_io_registry_entry_get_properties_bin_buf
|
|
+(
|
|
+ mach_port_t registry_entry,
|
|
+ mach_vm_address_t buf,
|
|
+ mach_vm_size_t *bufsize,
|
|
+ io_buf_ptr_t *properties,
|
|
+ mach_msg_type_number_t *propertiesCnt
|
|
+)
|
|
+{
|
|
+ IORegistryEntry* e = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
|
+ if (!e)
|
|
+ return kIOReturnBadArgument;
|
|
+
|
|
+ *propertiesCnt = 0;
|
|
+ *properties = nullptr;
|
|
+
|
|
+ // The caller's buffer lives in another task, so always answer out-of-line, which the
|
|
+ // protocol allows when the result does not go into 'buf'.
|
|
+ CFDataRef data = IOCFSerialize((CFTypeRef) e->getProperties(), kIOCFSerializeToBinary);
|
|
+ if (!data)
|
|
+ return kIOReturnNoMemory;
|
|
+
|
|
+ *propertiesCnt = CFDataGetLength(data);
|
|
+ kern_return_t kr = vm_allocate(mach_task_self(), (vm_address_t*) properties, *propertiesCnt, true);
|
|
+
|
|
+ if (kr == kIOReturnSuccess)
|
|
+ memcpy(*properties, CFDataGetBytePtr(data), *propertiesCnt);
|
|
+ else
|
|
+ *propertiesCnt = 0;
|
|
+
|
|
+ CFRelease(data);
|
|
+ return kr;
|
|
+}
|
|
+
|
|
+// OOL input ownership transfers to this server; release it on every exit path.
|
|
+kern_return_t is_io_registry_entry_from_path_ool(mach_port_t master_port, io_string_inband_t path,
|
|
+ io_buf_ptr_t path_ool, mach_msg_type_number_t path_oolCnt, kern_return_t* result, mach_port_t* registry_entry)
|
|
+{
|
|
+ struct Input {
|
|
+ io_buf_ptr_t bytes; mach_msg_type_number_t size;
|
|
+ ~Input() { if (bytes && size) vm_deallocate(mach_task_self(), (vm_address_t)bytes, size); }
|
|
+ } input {path_ool, path_oolCnt};
|
|
+ if (!result || !registry_entry) return kIOReturnBadArgument;
|
|
+ *registry_entry = MACH_PORT_NULL;
|
|
+ *result = kIOReturnBadArgument;
|
|
+ std::string_view fullPath;
|
|
+ if (path_oolCnt || path_ool) {
|
|
+ if (!path_ool || !path_oolCnt || path_oolCnt > 65536 || !path || path[0] ||
|
|
+ strnlen(path_ool, path_oolCnt) != path_oolCnt - 1) return kIOReturnSuccess;
|
|
+ fullPath = std::string_view(path_ool, path_oolCnt - 1);
|
|
+ } else {
|
|
+ if (!path || !memchr(path, 0, sizeof(io_string_inband_t))) return kIOReturnSuccess;
|
|
+ fullPath = path;
|
|
+ }
|
|
+ auto* entry = IORegistryEntry::fromPath(fullPath);
|
|
+ if (entry) { *registry_entry = entry->port(); *result = kIOReturnSuccess; }
|
|
+ else *result = kIOReturnNoDevice;
|
|
+ return kIOReturnSuccess;
|
|
+}
|
|
+
|
|
+kern_return_t is_io_registry_entry_get_path_ool(mach_port_t registry_entry, io_name_t plane,
|
|
+ io_string_inband_t path, io_buf_ptr_t* path_ool, mach_msg_type_number_t* path_oolCnt)
|
|
+{
|
|
+ if (!path || !path_ool || !path_oolCnt) return kIOReturnBadArgument;
|
|
+ path[0] = 0; *path_ool = nullptr; *path_oolCnt = 0;
|
|
+ auto* entry = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
|
+ if (!entry || !plane || !memchr(plane, 0, sizeof(io_name_t)) || !entry->inPlane(plane)) return kIOReturnBadArgument;
|
|
+ auto value = entry->getPath(plane);
|
|
+ if (value == "?unsup_plane?") return kIOReturnBadArgument;
|
|
+ if (value.size() < sizeof(io_string_inband_t)) {
|
|
+ strlcpy(path, value.c_str(), sizeof(io_string_inband_t));
|
|
+ } else {
|
|
+ // MIG uses physicalcopy with deallocate=false. The main queue completes
|
|
+ // each reply before its next request, so one bounded reusable buffer
|
|
+ // keeps the sender's bytes alive without leaking one VM allocation/call.
|
|
+ static std::string reply;
|
|
+ reply.assign(value);
|
|
+ *path_ool = const_cast<char*>(reply.data());
|
|
+ *path_oolCnt = reply.size(); // IOKit creates a CFString from bytes, excluding NUL
|
|
+ }
|
|
+ return kIOReturnSuccess;
|
|
+}
|
|
+
|
|
+kern_return_t is_io_registry_entry_in_plane(mach_port_t registry_entry, io_name_t plane, boolean_t* inPlane)
|
|
+{
|
|
+ if (!inPlane) return kIOReturnBadArgument;
|
|
+ *inPlane = FALSE;
|
|
+ auto* entry = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
|
+ if (!entry || !plane || !memchr(plane, 0, sizeof(io_name_t))) return kIOReturnBadArgument;
|
|
+ *inPlane = entry->inPlane(plane);
|
|
+ return kIOReturnSuccess;
|
|
+}
|
|
+
|
|
+kern_return_t is_io_registry_entry_get_property_bytes(mach_port_t registry_entry, io_name_t property_name,
|
|
+ io_struct_inband_t data, mach_msg_type_number_t* dataCnt)
|
|
+{
|
|
+ if (!dataCnt) return kIOReturnBadArgument;
|
|
+ size_t capacity = std::min<size_t>(*dataCnt, sizeof(io_struct_inband_t));
|
|
+ *dataCnt = 0;
|
|
+ auto* entry = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
|
+ if (!entry || !property_name || !memchr(property_name, 0, sizeof(io_name_t))) return kIOReturnBadArgument;
|
|
+ NSString* key = [NSString stringWithUTF8String:property_name];
|
|
+ if (!key) return kIOReturnBadArgument;
|
|
+ id value = [entry->getProperties() objectForKey:key];
|
|
+ if (!value) return kIOReturnNoResources;
|
|
+ const void* bytes = nullptr;
|
|
+ size_t length = 0;
|
|
+ uint64_t number = 0;
|
|
+ if ([value isKindOfClass:[NSData class]]) {
|
|
+ bytes = [value bytes]; length = [value length];
|
|
+ } else if ([value isKindOfClass:[NSString class]]) {
|
|
+ bytes = [value UTF8String];
|
|
+ if (!bytes) return kIOReturnBadArgument;
|
|
+ length = strlen((const char*)bytes) + 1;
|
|
+ } else if ([value isKindOfClass:[NSNumber class]] && CFGetTypeID((CFTypeRef)value) == CFBooleanGetTypeID()) {
|
|
+ bytes = [value boolValue] ? "Yes" : "No";
|
|
+ length = [value boolValue] ? sizeof("Yes") : sizeof("No");
|
|
+ } else if ([value isKindOfClass:[NSNumber class]]) {
|
|
+ const char* type = [value objCType];
|
|
+ switch (type ? type[0] : 0) {
|
|
+ case 'c': case 'C': case 'B': length = 1; break;
|
|
+ case 's': case 'S': length = 2; break;
|
|
+ case 'i': case 'I': length = 4; break;
|
|
+ case 'l': case 'L': length = sizeof(long); break;
|
|
+ case 'q': case 'Q': length = 8; break;
|
|
+ default: return kIOReturnBadArgument;
|
|
+ }
|
|
+ number = [value unsignedLongLongValue]; bytes = &number;
|
|
+ } else return kIOReturnBadArgument;
|
|
+ if (length > std::numeric_limits<mach_msg_type_number_t>::max()) return kIOReturnNoSpace;
|
|
+ if (length > capacity) { *dataCnt = capacity; return kIOReturnIPCError; }
|
|
+ *dataCnt = length;
|
|
+ if (length && !data) { *dataCnt = 0; return kIOReturnBadArgument; }
|
|
+ if (length) memcpy(data, bytes, length);
|
|
+ return kIOReturnSuccess;
|
|
+}
|
|
diff --git a/src/IOSurfaceRoot.mm b/src/IOSurfaceRoot.mm
|
|
index d7e6fab..d860582 100644
|
|
--- a/src/IOSurfaceRoot.mm
|
|
+++ b/src/IOSurfaceRoot.mm
|
|
@@ -21,13 +21,17 @@
|
|
#include "iokitd.h"
|
|
#include "IOSurface.h"
|
|
#include "IODisplayConnectX11.h"
|
|
+#include <EGL/eglext.h>
|
|
#include <IOKit/IOCFUnserialize.h>
|
|
#include <stdexcept>
|
|
#include <iostream>
|
|
|
|
IOSurfaceRoot::IOSurfaceRoot()
|
|
{
|
|
- m_display = eglGetDisplay(IODisplayConnectX11::getDisplay());
|
|
+ // The native display is an Xlib Display. eglGetDisplay() would let Mesa
|
|
+ // guess the platform from EGL_PLATFORM, which Wayland sessions often set
|
|
+ // to "wayland"; Mesa then treats the Display as a wl_display and crashes.
|
|
+ m_display = eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, IODisplayConnectX11::getDisplay(), NULL);
|
|
|
|
if (m_display == EGL_NO_DISPLAY)
|
|
return;
|
|
diff --git a/src/PowerSources.mm b/src/PowerSources.mm
|
|
new file mode 100644
|
|
index 0000000..70ca9bd
|
|
--- /dev/null
|
|
+++ b/src/PowerSources.mm
|
|
@@ -0,0 +1,139 @@
|
|
+extern "C" {
|
|
+#include "powermanagementServer.h"
|
|
+}
|
|
+#import <Foundation/NSArray.h>
|
|
+#import <Foundation/NSDictionary.h>
|
|
+#import <Foundation/NSNumber.h>
|
|
+#import <Foundation/NSString.h>
|
|
+#import <Foundation/NSData.h>
|
|
+#import <Foundation/NSPropertyList.h>
|
|
+#include <fstream>
|
|
+#include <string>
|
|
+#include <cstring>
|
|
+#include <dirent.h>
|
|
+#include <mach/mach.h>
|
|
+#include <vector>
|
|
+
|
|
+// The guest has no /sys; the host's is exposed under the system root
|
|
+static const char* const kPowerSupplyDir = "/Volumes/SystemRoot/sys/class/power_supply";
|
|
+
|
|
+static bool readSysfs(const std::string& dir, const char* name, std::string& value)
|
|
+{
|
|
+ std::ifstream file(dir + "/" + name);
|
|
+ if (!file || !std::getline(file, value))
|
|
+ return false;
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool readSysfsLong(const std::string& dir, const char* name, long long& value)
|
|
+{
|
|
+ std::string text;
|
|
+ if (!readSysfs(dir, name, text))
|
|
+ return false;
|
|
+ char* end = nullptr;
|
|
+ value = strtoll(text.c_str(), &end, 10);
|
|
+ return end != text.c_str();
|
|
+}
|
|
+
|
|
+static std::vector<std::string> powerSupplies(const char* wantedType)
|
|
+{
|
|
+ std::vector<std::string> result;
|
|
+ DIR* d = opendir(kPowerSupplyDir);
|
|
+ if (!d)
|
|
+ return result;
|
|
+
|
|
+ while (struct dirent* e = readdir(d))
|
|
+ {
|
|
+ if (e->d_name[0] == '.')
|
|
+ continue;
|
|
+ std::string dir = std::string(kPowerSupplyDir) + "/" + e->d_name;
|
|
+ std::string type;
|
|
+ if (readSysfs(dir, "type", type) && type == wantedType)
|
|
+ result.push_back(dir);
|
|
+ }
|
|
+ closedir(d);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static NSArray* copyInternalBatteries()
|
|
+{
|
|
+ bool onAC = false;
|
|
+ for (const std::string& ac : powerSupplies("Mains"))
|
|
+ {
|
|
+ long long online;
|
|
+ if (readSysfsLong(ac, "online", online) && online)
|
|
+ onAC = true;
|
|
+ }
|
|
+
|
|
+ NSMutableArray* sources = [NSMutableArray array];
|
|
+ for (const std::string& dir : powerSupplies("Battery"))
|
|
+ {
|
|
+ long long present = 1, capacity, energyNow, energyFull, powerNow, voltage;
|
|
+ std::string status;
|
|
+ readSysfsLong(dir, "present", present);
|
|
+ if (!present || !readSysfs(dir, "status", status) || !readSysfsLong(dir, "capacity", capacity))
|
|
+ continue;
|
|
+
|
|
+ NSMutableDictionary* ps = [NSMutableDictionary dictionary];
|
|
+ ps[@"Name"] = [NSString stringWithFormat: @"InternalBattery-%lu", (unsigned long) [sources count]];
|
|
+ ps[@"Type"] = @"InternalBattery";
|
|
+ ps[@"Transport Type"] = @"Internal";
|
|
+ ps[@"Power Source ID"] = @((int) [sources count]);
|
|
+ ps[@"Is Present"] = @YES;
|
|
+ ps[@"Current Capacity"] = @((int) capacity);
|
|
+ ps[@"Max Capacity"] = @100;
|
|
+ ps[@"Power Source State"] = onAC ? @"AC Power" : @"Battery Power";
|
|
+ ps[@"Is Charging"] = @(status == "Charging");
|
|
+ ps[@"Is Charged"] = @(status == "Full");
|
|
+
|
|
+ if (readSysfsLong(dir, "voltage_now", voltage))
|
|
+ ps[@"Voltage"] = @((int) (voltage / 1000)); // uV -> mV
|
|
+
|
|
+ // Remaining time in minutes from the energy and power draw reported by the host; -1 when unknown
|
|
+ long long minutes = -1;
|
|
+ if (readSysfsLong(dir, "power_now", powerNow) && powerNow > 0)
|
|
+ {
|
|
+ if (status == "Discharging" && readSysfsLong(dir, "energy_now", energyNow))
|
|
+ minutes = energyNow * 60 / powerNow;
|
|
+ else if (status == "Charging" && readSysfsLong(dir, "energy_now", energyNow) && readSysfsLong(dir, "energy_full", energyFull))
|
|
+ minutes = (energyFull - energyNow) * 60 / powerNow;
|
|
+ }
|
|
+ if (status == "Charging")
|
|
+ ps[@"Time to Full Charge"] = @((int) minutes);
|
|
+ else
|
|
+ ps[@"Time to Empty"] = @((int) minutes);
|
|
+
|
|
+ [sources addObject: ps];
|
|
+ }
|
|
+ return [sources copy];
|
|
+}
|
|
+
|
|
+kern_return_t _io_ps_copy_powersources_info
|
|
+(
|
|
+ mach_port_t server,
|
|
+ audit_token_t token,
|
|
+ int pstype,
|
|
+ vm_offset_t *powersources,
|
|
+ mach_msg_type_number_t *powersourcesCnt,
|
|
+ int *return_code
|
|
+)
|
|
+{
|
|
+ *powersources = 0;
|
|
+ *powersourcesCnt = 0;
|
|
+ *return_code = 0;
|
|
+
|
|
+ // The host only exposes internal batteries; there is no UPS data to report
|
|
+ NSArray* sources = copyInternalBatteries();
|
|
+ NSData* data = [NSPropertyListSerialization dataWithPropertyList: sources
|
|
+ format: NSPropertyListBinaryFormat_v1_0 options: 0 error: nil];
|
|
+ if (!data)
|
|
+ return KERN_FAILURE;
|
|
+
|
|
+ kern_return_t kr = vm_allocate(mach_task_self(), powersources, [data length], VM_FLAGS_ANYWHERE);
|
|
+ if (kr != KERN_SUCCESS)
|
|
+ return kr;
|
|
+
|
|
+ memcpy((void*) *powersources, [data bytes], [data length]);
|
|
+ *powersourcesCnt = [data length];
|
|
+ return KERN_SUCCESS;
|
|
+}
|
|
diff --git a/src/ServiceRegistry.h b/src/ServiceRegistry.h
|
|
index 34e74ad..0d0740b 100644
|
|
--- a/src/ServiceRegistry.h
|
|
+++ b/src/ServiceRegistry.h
|
|
@@ -32,6 +32,7 @@ public:
|
|
static ServiceRegistry* instance();
|
|
void registerService(IOService* service);
|
|
IOIterator* iteratorForMatchingServices(NSDictionary* criteria) const;
|
|
+ IOObject* firstMatchingService(NSDictionary* criteria) const;
|
|
private:
|
|
std::list<IOService*> m_registeredServices;
|
|
};
|
|
diff --git a/src/ServiceRegistry.mm b/src/ServiceRegistry.mm
|
|
index aaed046..7a190c3 100644
|
|
--- a/src/ServiceRegistry.mm
|
|
+++ b/src/ServiceRegistry.mm
|
|
@@ -24,6 +24,9 @@
|
|
#include <IOKit/IOReturn.h>
|
|
#include <os/log.h>
|
|
#include <stdexcept>
|
|
+#include <string>
|
|
+#include <cstring>
|
|
+#include <vector>
|
|
#include "IOIterator.h"
|
|
|
|
extern "C" {
|
|
@@ -49,6 +52,16 @@ IOIterator* ServiceRegistry::iteratorForMatchingServices(NSDictionary* criteria)
|
|
return new IOIterator(matching);
|
|
}
|
|
|
|
+IOObject* ServiceRegistry::firstMatchingService(NSDictionary* criteria) const
|
|
+{
|
|
+ for (IOService* service : m_registeredServices)
|
|
+ {
|
|
+ if (service->matches(criteria))
|
|
+ return service;
|
|
+ }
|
|
+ return nullptr;
|
|
+}
|
|
+
|
|
void ServiceRegistry::registerService(IOService* service)
|
|
{
|
|
m_registeredServices.push_back(service);
|
|
@@ -118,3 +131,91 @@ kern_return_t is_io_service_get_matching_services
|
|
// Old unsupported API
|
|
return kIOReturnUnsupported;
|
|
}
|
|
+
|
|
+static NSDictionary* unserializeMatching(const char* caller, io_struct_inband_t matching, mach_msg_type_number_t matchingCnt)
|
|
+{
|
|
+ CFStringRef errorString = nullptr;
|
|
+ CFTypeRef criteria = IOCFUnserializeBinary(matching, matchingCnt, nullptr, 0, &errorString);
|
|
+
|
|
+ if (!criteria)
|
|
+ throwCFStringException(CFSTR("cannot parse 'matching': %@"), errorString);
|
|
+
|
|
+ if (CFGetTypeID(criteria) != CFDictionaryGetTypeID())
|
|
+ {
|
|
+ CFRelease(criteria);
|
|
+ throw std::runtime_error(std::string(caller) + "(): dictionary expected");
|
|
+ }
|
|
+
|
|
+ return (NSDictionary*) criteria;
|
|
+}
|
|
+
|
|
+kern_return_t is_io_service_get_matching_service_bin
|
|
+(
|
|
+ mach_port_t master_port,
|
|
+ io_struct_inband_t matching,
|
|
+ mach_msg_type_number_t matchingCnt,
|
|
+ mach_port_t *service
|
|
+)
|
|
+{
|
|
+ *service = MACH_PORT_NULL;
|
|
+
|
|
+ try
|
|
+ {
|
|
+ NSDictionary* criteria = unserializeMatching("io_service_get_matching_service_bin", matching, matchingCnt);
|
|
+ IOObject* match = ServiceRegistry::instance()->firstMatchingService(criteria);
|
|
+ CFRelease(criteria);
|
|
+
|
|
+ if (!match)
|
|
+ return kIOReturnNotFound;
|
|
+
|
|
+ *service = match->port();
|
|
+ return kIOReturnSuccess;
|
|
+ }
|
|
+ catch (const std::exception& e)
|
|
+ {
|
|
+ os_log_error(OS_LOG_DEFAULT, "is_io_service_get_matching_service_bin: %s", e.what());
|
|
+ return kIOReturnBadArgument;
|
|
+ }
|
|
+}
|
|
+
|
|
+kern_return_t is_io_service_add_notification_bin_64
|
|
+(
|
|
+ mach_port_t master_port,
|
|
+ io_name_t notification_type,
|
|
+ io_struct_inband_t matching,
|
|
+ mach_msg_type_number_t matchingCnt,
|
|
+ mach_port_t wake_port,
|
|
+ io_async_ref64_t reference,
|
|
+ mach_msg_type_number_t referenceCnt,
|
|
+ mach_port_t *notification
|
|
+)
|
|
+{
|
|
+ *notification = MACH_PORT_NULL;
|
|
+
|
|
+ try
|
|
+ {
|
|
+ NSDictionary* criteria = unserializeMatching("io_service_add_notification_bin_64", matching, matchingCnt);
|
|
+ IOIterator* iterator;
|
|
+
|
|
+ // The registry is fixed once iokitd has started, so no service is ever published or
|
|
+ // terminated later. The notification object is the iterator of the services that match
|
|
+ // now (as in IOKit for the publish/matched types); termination types start out empty.
|
|
+ if (!strcmp(notification_type, "IOServicePublish") ||
|
|
+ !strcmp(notification_type, "IOServiceFirstMatch") ||
|
|
+ !strcmp(notification_type, "IOServiceMatched"))
|
|
+ iterator = ServiceRegistry::instance()->iteratorForMatchingServices(criteria);
|
|
+ else
|
|
+ iterator = new IOIterator(std::vector<IOObject*>());
|
|
+ CFRelease(criteria);
|
|
+
|
|
+ *notification = iterator->port();
|
|
+ iterator->releaseLater();
|
|
+
|
|
+ return kIOReturnSuccess;
|
|
+ }
|
|
+ catch (const std::exception& e)
|
|
+ {
|
|
+ os_log_error(OS_LOG_DEFAULT, "is_io_service_add_notification_bin_64: %s", e.what());
|
|
+ return kIOReturnBadArgument;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main.mm b/src/main.mm
|
|
index 37ce9b8..e23aea0 100644
|
|
--- a/src/main.mm
|
|
+++ b/src/main.mm
|
|
@@ -32,6 +32,8 @@
|
|
#include "IODisplayConnectX11.h"
|
|
#include "PowerAssertions.h"
|
|
#include "IOSurfaceRoot.h"
|
|
+#include "IOPlatformExpertDevice.h"
|
|
+#include "IOPMrootDomain.h"
|
|
|
|
extern "C" {
|
|
#include "iokitmigServer.h"
|
|
@@ -127,6 +129,8 @@ int main(int argc, const char** argv)
|
|
dispatch_resume(deathSource);
|
|
dispatch_resume(pwrMgmtPortSource);
|
|
|
|
+ IOPMrootDomain::instance()->startHostSleepMonitor();
|
|
+
|
|
os_log(OS_LOG_DEFAULT, "iokitd up and running.");
|
|
|
|
dispatch_main();
|
|
@@ -146,5 +150,7 @@ static void discoverAllDevices()
|
|
// Trick to make sure the root object gets instantiated first and gets the lowest ID
|
|
IORegistryEntry::root();
|
|
IODisplayConnectX11::discoverDevices(registry);
|
|
+ IOPlatformExpertDevice::registerSelf(registry);
|
|
IOSurfaceRoot::registerSelf(registry);
|
|
+ IOPMrootDomain::registerSelf(registry);
|
|
}
|
|
diff --git a/src/stubs-powermanagement.c b/src/stubs-powermanagement.c
|
|
index a568ecc..c2d4063 100644
|
|
--- a/src/stubs-powermanagement.c
|
|
+++ b/src/stubs-powermanagement.c
|
|
@@ -131,25 +131,6 @@ kern_return_t _io_pm_last_wake_time
|
|
}
|
|
|
|
|
|
-/* Routine io_pm_assertion_set_properties */
|
|
-
|
|
-kern_return_t _io_pm_assertion_set_properties
|
|
-(
|
|
- mach_port_t server,
|
|
- audit_token_t token,
|
|
- int assertion_id,
|
|
- vm_offset_t props,
|
|
- mach_msg_type_number_t propsCnt,
|
|
- int *disableAppSleep,
|
|
- int *enableAppSleep,
|
|
- int *enTrIntensity,
|
|
- int *return_code
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
-
|
|
/* Routine io_pm_assertion_copy_details */
|
|
|
|
kern_return_t _io_pm_assertion_copy_details
|
|
@@ -368,22 +349,6 @@ kern_return_t _io_ps_release_pspowersource
|
|
}
|
|
|
|
|
|
-/* Routine io_ps_copy_powersources_info */
|
|
-
|
|
-kern_return_t _io_ps_copy_powersources_info
|
|
-(
|
|
- mach_port_t server,
|
|
- audit_token_t token,
|
|
- int pstype,
|
|
- vm_offset_t *powersources,
|
|
- mach_msg_type_number_t *powersourcesCnt,
|
|
- int *return_code
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
-
|
|
|
|
/* Routine io_ps_copy_chargelog */
|
|
|
|
diff --git a/src/stubs.c b/src/stubs.c
|
|
index 91d11a1..5a00091 100644
|
|
--- a/src/stubs.c
|
|
+++ b/src/stubs.c
|
|
@@ -33,26 +33,7 @@ kern_return_t is_io_registry_iterator_exit_entry
|
|
return KERN_NOT_SUPPORTED;
|
|
}
|
|
|
|
-kern_return_t is_io_registry_entry_get_property_bytes
|
|
-(
|
|
- mach_port_t registry_entry,
|
|
- io_name_t property_name,
|
|
- io_struct_inband_t data,
|
|
- mach_msg_type_number_t *dataCnt
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
|
|
-kern_return_t is_io_service_close
|
|
-(
|
|
- mach_port_t connection
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
|
|
kern_return_t is_io_connect_get_service
|
|
(
|
|
@@ -124,16 +105,7 @@ kern_return_t is_io_registry_entry_set_properties
|
|
return KERN_NOT_SUPPORTED;
|
|
}
|
|
|
|
-kern_return_t is_io_registry_entry_in_plane
|
|
-(
|
|
- mach_port_t registry_entry,
|
|
- io_name_t plane,
|
|
- boolean_t *inPlane
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
+
|
|
|
|
kern_return_t is_io_service_get_busy_state
|
|
(
|
|
@@ -325,20 +297,6 @@ kern_return_t is_io_service_add_notification
|
|
return KERN_NOT_SUPPORTED;
|
|
}
|
|
|
|
-kern_return_t is_io_service_add_interest_notification
|
|
-(
|
|
- mach_port_t service,
|
|
- io_name_t type_of_interest,
|
|
- mach_port_t wake_port,
|
|
- io_async_ref_t reference,
|
|
- mach_msg_type_number_t referenceCnt,
|
|
- mach_port_t *notification
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
-
|
|
kern_return_t is_io_service_acknowledge_notification
|
|
(
|
|
mach_port_t service,
|
|
@@ -439,22 +397,6 @@ kern_return_t is_io_object_get_bundle_identifier
|
|
return KERN_NOT_SUPPORTED;
|
|
}
|
|
|
|
-kern_return_t is_io_service_open_extended
|
|
-(
|
|
- mach_port_t service,
|
|
- task_t owningTask,
|
|
- uint32_t connect_type,
|
|
- NDR_record_t ndr,
|
|
- io_buf_ptr_t properties,
|
|
- mach_msg_type_number_t propertiesCnt,
|
|
- kern_return_t *result,
|
|
- mach_port_t *connection
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
-
|
|
kern_return_t is_io_connect_map_memory_into_task
|
|
(
|
|
mach_port_t connection,
|
|
@@ -533,20 +475,6 @@ kern_return_t is_io_service_add_notification_64
|
|
return KERN_NOT_SUPPORTED;
|
|
}
|
|
|
|
-kern_return_t is_io_service_add_interest_notification_64
|
|
-(
|
|
- mach_port_t service,
|
|
- io_name_t type_of_interest,
|
|
- mach_port_t wake_port,
|
|
- io_async_ref64_t reference,
|
|
- mach_msg_type_number_t referenceCnt,
|
|
- mach_port_t *notification
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
-
|
|
kern_return_t is_io_service_add_notification_ool_64
|
|
(
|
|
mach_port_t master_port,
|
|
@@ -642,18 +570,6 @@ kern_return_t is_io_server_version
|
|
}
|
|
|
|
|
|
-kern_return_t is_io_service_get_matching_service_bin
|
|
-(
|
|
- mach_port_t master_port,
|
|
- io_struct_inband_t matching,
|
|
- mach_msg_type_number_t matchingCnt,
|
|
- mach_port_t *service
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
-
|
|
kern_return_t is_io_service_match_property_table_bin
|
|
(
|
|
mach_port_t service,
|
|
@@ -682,48 +598,9 @@ kern_return_t is_io_service_add_notification_bin
|
|
return KERN_NOT_SUPPORTED;
|
|
}
|
|
|
|
-kern_return_t is_io_service_add_notification_bin_64
|
|
-(
|
|
- mach_port_t master_port,
|
|
- io_name_t notification_type,
|
|
- io_struct_inband_t matching,
|
|
- mach_msg_type_number_t matchingCnt,
|
|
- mach_port_t wake_port,
|
|
- io_async_ref64_t reference,
|
|
- mach_msg_type_number_t referenceCnt,
|
|
- mach_port_t *notification
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
|
|
-kern_return_t is_io_registry_entry_from_path_ool
|
|
-(
|
|
- mach_port_t master_port,
|
|
- io_string_inband_t path,
|
|
- io_buf_ptr_t path_ool,
|
|
- mach_msg_type_number_t path_oolCnt,
|
|
- kern_return_t *result,
|
|
- mach_port_t *registry_entry
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
|
|
-kern_return_t is_io_registry_entry_get_path_ool
|
|
- (
|
|
- mach_port_t registry_entry,
|
|
- io_name_t plane,
|
|
- io_string_inband_t path,
|
|
- io_buf_ptr_t *path_ool,
|
|
- mach_msg_type_number_t *path_oolCnt
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
+
|
|
|
|
kern_return_t is_io_device_tree_entry_exists_with_name
|
|
(
|
|
@@ -736,31 +613,3 @@ kern_return_t is_io_device_tree_entry_exists_with_name
|
|
return KERN_NOT_SUPPORTED;
|
|
}
|
|
|
|
-kern_return_t is_io_registry_entry_get_properties_bin_buf
|
|
-(
|
|
- mach_port_t registry_entry,
|
|
- mach_vm_address_t buf,
|
|
- mach_vm_size_t *bufsize,
|
|
- io_buf_ptr_t *properties,
|
|
- mach_msg_type_number_t *propertiesCnt
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
-
|
|
-kern_return_t is_io_registry_entry_get_property_bin_buf
|
|
-(
|
|
- mach_port_t registry_entry,
|
|
- io_name_t plane,
|
|
- io_name_t property_name,
|
|
- uint32_t options,
|
|
- mach_vm_address_t buf,
|
|
- mach_vm_size_t *bufsize,
|
|
- io_buf_ptr_t *properties,
|
|
- mach_msg_type_number_t *propertiesCnt
|
|
-)
|
|
-{
|
|
- STUB();
|
|
- return KERN_NOT_SUPPORTED;
|
|
-}
|
|
diff --git a/src/IOPlatformExpertDevice.h b/src/IOPlatformExpertDevice.h
|
|
new file mode 100644
|
|
index 0000000..38ad635
|
|
--- /dev/null
|
|
+++ b/src/IOPlatformExpertDevice.h
|
|
@@ -0,0 +1,21 @@
|
|
+#ifndef IOKITD_IOPLATFORMEXPERTDEVICE_H
|
|
+#define IOKITD_IOPLATFORMEXPERTDEVICE_H
|
|
+
|
|
+#include "IOService.h"
|
|
+#include "ServiceRegistry.h"
|
|
+
|
|
+// The root platform device (what IOServiceMatching("IOPlatformExpertDevice") finds on macOS).
|
|
+// Its model strings name a fixed MacBookPro16,1. IOPlatformUUID and IOPlatformSerialNumber are
|
|
+// derived from the host machine-id, so they differ per host and stay stable across reinstalls.
|
|
+class IOPlatformExpertDevice : public IOService
|
|
+{
|
|
+public:
|
|
+ const char* className() const override;
|
|
+ NSDictionary* matchingDictionary() override;
|
|
+ NSDictionary* getProperties() override;
|
|
+ bool conformsTo(const char* className) override;
|
|
+
|
|
+ static void registerSelf(ServiceRegistry* targetServiceRegistry);
|
|
+};
|
|
+
|
|
+#endif
|
|
diff --git a/src/IOPlatformExpertDevice.mm b/src/IOPlatformExpertDevice.mm
|
|
new file mode 100644
|
|
index 0000000..71b9f4d
|
|
--- /dev/null
|
|
+++ b/src/IOPlatformExpertDevice.mm
|
|
@@ -0,0 +1,98 @@
|
|
+#include "IOPlatformExpertDevice.h"
|
|
+#include "PlatformIdentity.h"
|
|
+#import <Foundation/NSString.h>
|
|
+#import <Foundation/NSData.h>
|
|
+#import <Foundation/NSArray.h>
|
|
+#import <Foundation/NSCharacterSet.h>
|
|
+#import <Foundation/NSDictionary.h>
|
|
+#include <CommonCrypto/CommonDigest.h>
|
|
+#include <uuid/uuid.h>
|
|
+#include <unistd.h>
|
|
+#include <cstring>
|
|
+
|
|
+// Platform expert strings are NUL-terminated data
|
|
+static NSData* dataForString(NSString* string)
|
|
+{
|
|
+ const char* utf8 = [string UTF8String];
|
|
+ return [NSData dataWithBytes: utf8 length: strlen(utf8) + 1];
|
|
+}
|
|
+
|
|
+const char* IOPlatformExpertDevice::className() const
|
|
+{
|
|
+ return "IOPlatformExpertDevice";
|
|
+}
|
|
+
|
|
+NSDictionary* IOPlatformExpertDevice::matchingDictionary()
|
|
+{
|
|
+ return @{
|
|
+ @"IOProviderClass": @"IOPlatformExpertDevice",
|
|
+ @"IOClass": @"IOPlatformExpertDevice",
|
|
+ };
|
|
+}
|
|
+
|
|
+// Per-host identifiers, stable like a real Mac's: a SHA-256 of the host machine-id and a fixed
|
|
+// string, so the raw machine-id (which machine-id(5) says not to expose) never reaches apps.
|
|
+// The serial's "DARLING-" form is longer than any Apple serial and cannot match a real Mac.
|
|
+static bool hostIdentifiers(NSString** uuid, NSString** serial)
|
|
+{
|
|
+ static const char app[] = "darling-lol IOPlatformExpertDevice";
|
|
+ uuid_t machineID;
|
|
+ struct timespec wait = { 0, 0 };
|
|
+ if (gethostuuid(machineID, &wait) != 0)
|
|
+ return false;
|
|
+
|
|
+ unsigned char input[sizeof(machineID) + sizeof(app)];
|
|
+ std::memcpy(input, machineID, sizeof(machineID));
|
|
+ std::memcpy(input + sizeof(machineID), app, sizeof(app));
|
|
+ unsigned char hash[CC_SHA256_DIGEST_LENGTH];
|
|
+ CC_SHA256(input, sizeof(input), hash);
|
|
+
|
|
+ hash[6] = (hash[6] & 0x0f) | 0x40; // version 4
|
|
+ hash[8] = (hash[8] & 0x3f) | 0x80; // RFC 4122 variant
|
|
+ uuid_string_t text;
|
|
+ uuid_unparse_upper(hash, text);
|
|
+ // Owned (not autoreleased): the caller keeps them in statics for the daemon's lifetime
|
|
+ *uuid = [[NSString alloc] initWithUTF8String: text];
|
|
+ *serial = [[NSString alloc] initWithFormat: @"DARLING-%02X%02X%02X%02X", hash[16], hash[17], hash[18], hash[19]];
|
|
+ return true;
|
|
+}
|
|
+
|
|
+NSDictionary* IOPlatformExpertDevice::getProperties()
|
|
+{
|
|
+ // MacBookPro16,1 model strings; the Linux DMI values describe the host hardware, not the Mac we emulate
|
|
+ NSMutableDictionary* properties = [NSMutableDictionary dictionaryWithDictionary: @{
|
|
+ @"manufacturer": dataForString(@"Apple Inc."),
|
|
+ @"model": dataForString(@"MacBookPro16,1"),
|
|
+ @"product-name": dataForString(@"MacBookPro16,1"),
|
|
+ @"board-id": dataForString(@"Mac-E1008331FDC96864"),
|
|
+ }];
|
|
+
|
|
+ static NSString* uuid;
|
|
+ static NSString* serial;
|
|
+ static bool known = hostIdentifiers(&uuid, &serial);
|
|
+ if (known)
|
|
+ {
|
|
+ properties[@"IOPlatformUUID"] = uuid;
|
|
+ properties[@"IOPlatformSerialNumber"] = serial;
|
|
+ }
|
|
+ // Opt-in, immutable startup snapshot. No configuration preserves the existing
|
|
+ // per-host identifiers; daemon restart is the explicit activation boundary.
|
|
+ static NSDictionary* overrides = [DarlingPlatformIdentity::load("/private/etc/darling-platform.plist") copy];
|
|
+ if (overrides) [properties addEntriesFromDictionary:overrides];
|
|
+ return properties;
|
|
+}
|
|
+
|
|
+bool IOPlatformExpertDevice::conformsTo(const char* className)
|
|
+{
|
|
+ if (std::strcmp(className, "IOPlatformDevice") == 0)
|
|
+ return true;
|
|
+ return IOService::conformsTo(className);
|
|
+}
|
|
+
|
|
+void IOPlatformExpertDevice::registerSelf(ServiceRegistry* targetServiceRegistry)
|
|
+{
|
|
+ static IOPlatformExpertDevice* device = new IOPlatformExpertDevice;
|
|
+
|
|
+ targetServiceRegistry->registerService(device);
|
|
+ device->registerInPlane(kIOServicePlane, "Platform", IORegistryEntry::root());
|
|
+}
|
|
diff --git a/src/IOInterestNotification.h b/src/IOInterestNotification.h
|
|
new file mode 100644
|
|
index 0000000..62be712
|
|
--- /dev/null
|
|
+++ b/src/IOInterestNotification.h
|
|
@@ -0,0 +1,66 @@
|
|
+/*
|
|
+ This file is part of Darling.
|
|
+
|
|
+ Copyright (C) 2026 Darling developers
|
|
+
|
|
+ Darling is free software: you can redistribute it and/or modify
|
|
+ it under the terms of the GNU General Public License as published by
|
|
+ the Free Software Foundation, either version 3 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ Darling is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU General Public License
|
|
+ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
+*/
|
|
+
|
|
+#ifndef IOKITD_IOINTERESTNOTIFICATION_H
|
|
+#define IOKITD_IOINTERESTNOTIFICATION_H
|
|
+#include "IOObject.h"
|
|
+#include <IOKit/OSMessageNotification.h>
|
|
+#include <string>
|
|
+#include <deque>
|
|
+
|
|
+class IOService;
|
|
+
|
|
+// The object returned by IOServiceAddInterestNotification() (the kernel's
|
|
+// IOServiceMessageUserNotification). It lives until the client releases it,
|
|
+// and sends service messages to the client's notification port in the same
|
|
+// layout as the kernel, which IODispatchCalloutFromMessage() decodes.
|
|
+class IOInterestNotification : public IOObject
|
|
+{
|
|
+public:
|
|
+ // Takes over the send right to wakePort. reference holds referenceSize bytes
|
|
+ // of the client's async reference (io_async_ref64_t or io_async_ref_t).
|
|
+ IOInterestNotification(IOService* service, const char* interestType, mach_port_t wakePort,
|
|
+ const void* reference, size_t referenceSize, bool client64);
|
|
+ ~IOInterestNotification();
|
|
+
|
|
+ const char* className() const override;
|
|
+
|
|
+ const std::string& interestType() const { return m_interestType; }
|
|
+ IOService* service() const { return m_service; }
|
|
+
|
|
+ // Queues messageType without blocking; retries in order when the port is full.
|
|
+ kern_return_t deliver(uint32_t messageType, uint64_t argument);
|
|
+private:
|
|
+ kern_return_t sendMessage(uint32_t messageType, uint64_t argument);
|
|
+ kern_return_t flushMessages();
|
|
+ void scheduleRetry();
|
|
+ bool wakePortIsAlive() const;
|
|
+ struct Message { uint32_t type; uint64_t argument; };
|
|
+ std::deque<Message> m_messages;
|
|
+ bool m_retryScheduled = false;
|
|
+ bool m_failed = false;
|
|
+ IOService* m_service;
|
|
+ std::string m_interestType;
|
|
+ mach_port_t m_wakePort;
|
|
+ OSAsyncReference64 m_reference;
|
|
+ size_t m_referenceSize;
|
|
+ bool m_client64;
|
|
+};
|
|
+
|
|
+#endif
|
|
diff --git a/src/IOInterestNotification.mm b/src/IOInterestNotification.mm
|
|
new file mode 100644
|
|
index 0000000..1aa41a9
|
|
--- /dev/null
|
|
+++ b/src/IOInterestNotification.mm
|
|
@@ -0,0 +1,286 @@
|
|
+/*
|
|
+ This file is part of Darling.
|
|
+
|
|
+ Copyright (C) 2026 Darling developers
|
|
+
|
|
+ Darling is free software: you can redistribute it and/or modify
|
|
+ it under the terms of the GNU General Public License as published by
|
|
+ the Free Software Foundation, either version 3 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ Darling is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU General Public License
|
|
+ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
+*/
|
|
+
|
|
+#include "IOInterestNotification.h"
|
|
+#include "IOService.h"
|
|
+#include "iokitd.h"
|
|
+#include <IOKit/IOReturn.h>
|
|
+#include <os/log.h>
|
|
+#include <algorithm>
|
|
+#include <cstddef>
|
|
+#include <cstring>
|
|
+#include <new>
|
|
+
|
|
+extern "C" {
|
|
+#include "iokitmigServer.h"
|
|
+}
|
|
+
|
|
+// Mirrors xnu's IOServiceMessageUserNotification::PingMsg: the provider is
|
|
+// passed as a port descriptor, followed by the notification header, the
|
|
+// client's async reference and the message content.
|
|
+struct InterestMessageHead
|
|
+{
|
|
+ mach_msg_header_t header;
|
|
+ mach_msg_body_t body;
|
|
+ mach_msg_port_descriptor_t provider;
|
|
+ mach_msg_size_t size;
|
|
+ natural_t type;
|
|
+};
|
|
+
|
|
+// The kernel's kIOUserNotifyMaxMessageSize, reported in the header's size field.
|
|
+static const mach_msg_size_t kMaxMessageArgumentSize = 64;
|
|
+
|
|
+// IODispatchCalloutFromMessage() finds the notification header right after the descriptors.
|
|
+static_assert(offsetof(InterestMessageHead, size) == sizeof(mach_msg_header_t) + sizeof(mach_msg_body_t)
|
|
+ + sizeof(mach_msg_port_descriptor_t), "notification header must follow the port descriptor");
|
|
+static_assert(offsetof(OSNotificationHeader64, reference) == 2 * sizeof(natural_t), "unexpected header layout");
|
|
+static_assert(sizeof(IOServiceInterestContent64) == sizeof(natural_t) + sizeof(io_user_reference_t),
|
|
+ "unexpected interest content layout");
|
|
+
|
|
+IOInterestNotification::IOInterestNotification(IOService* service, const char* interestType,
|
|
+ mach_port_t wakePort, const void* reference, size_t referenceSize, bool client64)
|
|
+: m_service(service), m_interestType(interestType), m_wakePort(wakePort),
|
|
+ m_referenceSize(std::min(referenceSize, sizeof(m_reference))), m_client64(client64)
|
|
+{
|
|
+ std::memset(m_reference, 0, sizeof(m_reference));
|
|
+ std::memcpy(m_reference, reference, m_referenceSize);
|
|
+ m_service->addInterest(this);
|
|
+}
|
|
+
|
|
+IOInterestNotification::~IOInterestNotification()
|
|
+{
|
|
+ m_service->removeInterest(this);
|
|
+ if (MACH_PORT_VALID(m_wakePort))
|
|
+ mach_port_deallocate(mach_task_self(), m_wakePort);
|
|
+}
|
|
+
|
|
+const char* IOInterestNotification::className() const
|
|
+{
|
|
+ return "IOServiceMessageUserNotification";
|
|
+}
|
|
+
|
|
+kern_return_t IOInterestNotification::sendMessage(uint32_t messageType, uint64_t argument)
|
|
+{
|
|
+ // Header, reference (at most OSAsyncReference64) and IOServiceInterestContent64
|
|
+ alignas(8) uint8_t buffer[sizeof(InterestMessageHead) + sizeof(OSAsyncReference64)
|
|
+ + sizeof(IOServiceInterestContent64)];
|
|
+ std::memset(buffer, 0, sizeof(buffer));
|
|
+
|
|
+ InterestMessageHead* head = reinterpret_cast<InterestMessageHead*>(buffer);
|
|
+ head->header.msgh_bits = MACH_MSGH_BITS_COMPLEX
|
|
+ | MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND);
|
|
+ head->header.msgh_remote_port = m_wakePort;
|
|
+ // Received as msgh_remote_port: the client checks that it still holds this notification.
|
|
+ head->header.msgh_local_port = port();
|
|
+ head->header.msgh_id = kOSNotificationMessageID;
|
|
+
|
|
+ head->body.msgh_descriptor_count = 1;
|
|
+ head->provider.name = m_service->port();
|
|
+ head->provider.disposition = MACH_MSG_TYPE_MAKE_SEND;
|
|
+ head->provider.type = MACH_MSG_PORT_DESCRIPTOR;
|
|
+
|
|
+ // A pointer-sized argument: 8 bytes for 64-bit clients, 4 otherwise.
|
|
+ // The low bits of the type would carry (size & 3), which is 0 for both.
|
|
+ size_t argumentSize = m_client64 ? sizeof(io_user_reference_t) : sizeof(uint32_t);
|
|
+ head->size = kMaxMessageArgumentSize + sizeof(IOServiceInterestContent64);
|
|
+ head->type = kIOServiceMessageNotificationType;
|
|
+
|
|
+ uint8_t* p = buffer + sizeof(InterestMessageHead);
|
|
+ std::memcpy(p, m_reference, m_referenceSize);
|
|
+ p += m_referenceSize;
|
|
+
|
|
+ natural_t type = messageType;
|
|
+ std::memcpy(p, &type, sizeof(type));
|
|
+ p += sizeof(type);
|
|
+ if (m_client64)
|
|
+ std::memcpy(p, &argument, sizeof(argument));
|
|
+ else
|
|
+ {
|
|
+ uint32_t argument32 = (uint32_t) argument;
|
|
+ std::memcpy(p, &argument32, sizeof(argument32));
|
|
+ }
|
|
+ p += argumentSize;
|
|
+
|
|
+ head->header.msgh_size = (mach_msg_size_t) (p - buffer);
|
|
+
|
|
+ // Never block iokitd on a client that does not drain its port.
|
|
+ kern_return_t kr = mach_msg(&head->header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, head->header.msgh_size,
|
|
+ 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
|
|
+
|
|
+ if (kr != KERN_SUCCESS)
|
|
+ {
|
|
+ // A pseudo-receive on failed delivery returns newly copied rights.
|
|
+ // mach_msg_destroy handles the remote right/descriptors, but not local.
|
|
+ mach_msg_type_name_t local = MACH_MSGH_BITS_LOCAL(head->header.msgh_bits);
|
|
+ if (local == MACH_MSG_TYPE_MOVE_SEND || local == MACH_MSG_TYPE_MOVE_SEND_ONCE)
|
|
+ mach_port_deallocate(mach_task_self(), head->header.msgh_local_port);
|
|
+ mach_msg_destroy(&head->header);
|
|
+ }
|
|
+ return kr;
|
|
+}
|
|
+
|
|
+bool IOInterestNotification::wakePortIsAlive() const
|
|
+{
|
|
+ mach_port_type_t type = 0;
|
|
+ return mach_port_type(mach_task_self(), m_wakePort, &type) == KERN_SUCCESS
|
|
+ && (type & MACH_PORT_TYPE_SEND);
|
|
+}
|
|
+
|
|
+void IOInterestNotification::scheduleRetry()
|
|
+{
|
|
+ if (m_retryScheduled)
|
|
+ return;
|
|
+ m_retryScheduled = true;
|
|
+ mach_port_t notificationPort = port();
|
|
+ // Do not retain a send right while waiting: deregistration must still
|
|
+ // destroy this object. Look it up again on the same serial queue.
|
|
+ IOInterestNotification* expected = this;
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
|
|
+ auto current = dynamic_cast<IOInterestNotification*>(IOObject::lookup(notificationPort));
|
|
+ if (current != expected)
|
|
+ return;
|
|
+ current->m_retryScheduled = false;
|
|
+ current->flushMessages();
|
|
+ });
|
|
+}
|
|
+
|
|
+kern_return_t IOInterestNotification::flushMessages()
|
|
+{
|
|
+ while (!m_messages.empty())
|
|
+ {
|
|
+ kern_return_t kr = MACH_SEND_INVALID_DEST;
|
|
+ if (wakePortIsAlive())
|
|
+ {
|
|
+ const auto& message = m_messages.front();
|
|
+ kr = sendMessage(message.type, message.argument);
|
|
+ // Mach can report success if the destination died during the send.
|
|
+ if (!wakePortIsAlive())
|
|
+ kr = MACH_SEND_INVALID_DEST;
|
|
+ }
|
|
+ if (kr == MACH_SEND_TIMED_OUT || kr == MACH_SEND_INTERRUPTED || kr == MACH_SEND_NO_BUFFER)
|
|
+ {
|
|
+ scheduleRetry();
|
|
+ return KERN_SUCCESS; // accepted; preserve message order until the port drains
|
|
+ }
|
|
+ if (kr != KERN_SUCCESS)
|
|
+ {
|
|
+ m_failed = true;
|
|
+ m_messages.clear();
|
|
+ os_log_error(OS_LOG_DEFAULT, "iokitd: notification destination unavailable: 0x%x", kr);
|
|
+ mach_port_t notificationPort = port();
|
|
+ IOInterestNotification* expected = this;
|
|
+ // Defer removal until tellApps has finished building its pending set.
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
+ auto current = dynamic_cast<IOInterestNotification*>(IOObject::lookup(notificationPort));
|
|
+ if (current == expected)
|
|
+ current->m_service->removeInterest(current);
|
|
+ });
|
|
+ return kr;
|
|
+ }
|
|
+ m_messages.pop_front();
|
|
+ }
|
|
+ return KERN_SUCCESS;
|
|
+}
|
|
+
|
|
+kern_return_t IOInterestNotification::deliver(uint32_t messageType, uint64_t argument)
|
|
+{
|
|
+ if (m_failed)
|
|
+ return MACH_SEND_INVALID_DEST;
|
|
+ m_messages.push_back({messageType, argument});
|
|
+ return flushMessages();
|
|
+}
|
|
+
|
|
+static kern_return_t addInterestNotification(mach_port_t service, const char* interestType,
|
|
+ mach_port_t wakePort, const void* reference, size_t referenceSize, bool client64,
|
|
+ mach_port_t* notification)
|
|
+{
|
|
+ *notification = MACH_PORT_NULL;
|
|
+
|
|
+ // On failure dispatch_mig_server destroys the request, including wakePort.
|
|
+ // Only a successful registration takes ownership of that send right.
|
|
+ IOService* s = dynamic_cast<IOService*>(IOObject::lookup(service));
|
|
+ if (!s)
|
|
+ return kIOReturnBadArgument;
|
|
+ if (!MACH_PORT_VALID(wakePort))
|
|
+ return kIOReturnBadArgument;
|
|
+ if (!s->acceptsInterest(interestType))
|
|
+ return kIOReturnUnsupported;
|
|
+
|
|
+ IOInterestNotification* n;
|
|
+ try
|
|
+ {
|
|
+ n = new IOInterestNotification(s, interestType, wakePort, reference, referenceSize, client64);
|
|
+ }
|
|
+ catch (const std::exception& e)
|
|
+ {
|
|
+ os_log_error(OS_LOG_DEFAULT, "iokitd: cannot create interest notification: %s", e.what());
|
|
+ return kIOReturnNoResources;
|
|
+ }
|
|
+
|
|
+ *notification = n->port();
|
|
+ // The reply carries a copy of the send right; drop ours afterwards.
|
|
+ n->releaseLater();
|
|
+ return kIOReturnSuccess;
|
|
+}
|
|
+
|
|
+kern_return_t is_io_service_add_interest_notification_64
|
|
+(
|
|
+ mach_port_t service,
|
|
+ io_name_t type_of_interest,
|
|
+ mach_port_t wake_port,
|
|
+ io_async_ref64_t reference,
|
|
+ mach_msg_type_number_t referenceCnt,
|
|
+ mach_port_t *notification
|
|
+)
|
|
+{
|
|
+ if (referenceCnt > kOSAsyncRef64Count)
|
|
+ {
|
|
+ *notification = MACH_PORT_NULL;
|
|
+ return kIOReturnBadArgument;
|
|
+ }
|
|
+
|
|
+ OSAsyncReference64 zreference = {};
|
|
+ std::memcpy(zreference, reference, referenceCnt * sizeof(zreference[0]));
|
|
+
|
|
+ return addInterestNotification(service, type_of_interest, wake_port, zreference,
|
|
+ sizeof(zreference), true, notification);
|
|
+}
|
|
+
|
|
+kern_return_t is_io_service_add_interest_notification
|
|
+(
|
|
+ mach_port_t service,
|
|
+ io_name_t type_of_interest,
|
|
+ mach_port_t wake_port,
|
|
+ io_async_ref_t reference,
|
|
+ mach_msg_type_number_t referenceCnt,
|
|
+ mach_port_t *notification
|
|
+)
|
|
+{
|
|
+ if (referenceCnt > kOSAsyncRefCount)
|
|
+ {
|
|
+ *notification = MACH_PORT_NULL;
|
|
+ return kIOReturnBadArgument;
|
|
+ }
|
|
+
|
|
+ io_async_ref_t zreference = {};
|
|
+ std::memcpy(zreference, reference, referenceCnt * sizeof(zreference[0]));
|
|
+
|
|
+ return addInterestNotification(service, type_of_interest, wake_port, zreference,
|
|
+ sizeof(zreference), false, notification);
|
|
+}
|
|
diff --git a/src/IOObject.cpp b/src/IOObject.cpp
|
|
index 1a40da6..232f150 100644
|
|
--- a/src/IOObject.cpp
|
|
+++ b/src/IOObject.cpp
|
|
@@ -50,12 +50,17 @@ IOObject::IOObject()
|
|
throw std::runtime_error("Failed to create dispatch source");
|
|
|
|
dispatch_source_set_event_handler(m_dispatchSource, ^{
|
|
- dispatch_mig_server(m_dispatchSource, is_iokit_subsystem.maxsize, iokit_server);
|
|
+ dispatch_mig_server(m_dispatchSource, is_iokit_subsystem.maxsize,
|
|
+ [](mach_msg_header_t* request, mach_msg_header_t* reply) -> boolean_t {
|
|
+ if (request->msgh_id == MACH_NOTIFY_NO_SENDERS)
|
|
+ return IOObject::deathNotify(request, reply);
|
|
+ return iokit_server(request, reply);
|
|
+ });
|
|
});
|
|
dispatch_resume(m_dispatchSource);
|
|
|
|
mach_port_t oldTargetOfNotification = MACH_PORT_NULL;
|
|
- kr = mach_port_request_notification(task, m_port, MACH_NOTIFY_NO_SENDERS, 1, g_deathPort, MACH_MSG_TYPE_MAKE_SEND_ONCE, &oldTargetOfNotification);
|
|
+ kr = mach_port_request_notification(task, m_port, MACH_NOTIFY_NO_SENDERS, 1, m_port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &oldTargetOfNotification);
|
|
if (kr != KERN_SUCCESS)
|
|
throw std::runtime_error("Failed to setup MACH_NOTIFY_NO_SENDERS");
|
|
|
|
@@ -64,12 +69,15 @@ IOObject::IOObject()
|
|
|
|
IOObject::~IOObject()
|
|
{
|
|
- if (m_dispatchSource)
|
|
- dispatch_release(m_dispatchSource);
|
|
-
|
|
m_objects.erase(m_port);
|
|
-
|
|
- mach_port_deallocate(mach_task_self(), m_port);
|
|
+ // A receive source must be cancelled before its receive right is destroyed.
|
|
+ // The cancellation handler captures the port value, never the dying object.
|
|
+ mach_port_t port = m_port;
|
|
+ dispatch_source_set_cancel_handler(m_dispatchSource, ^{
|
|
+ mach_port_destroy(mach_task_self(), port);
|
|
+ });
|
|
+ dispatch_source_cancel(m_dispatchSource);
|
|
+ dispatch_release(m_dispatchSource);
|
|
}
|
|
|
|
void IOObject::retain()
|
|
@@ -113,12 +121,30 @@ boolean_t IOObject::deathNotify(mach_msg_header_t *request, mach_msg_header_t *r
|
|
|
|
if (Request->not_header.msgh_id == MACH_NOTIFY_NO_SENDERS)
|
|
{
|
|
- IOObject* object = lookup(request->msgh_remote_port);
|
|
-
|
|
- if (object != nullptr)
|
|
+ // No-senders messages carry no object name in their body. They are
|
|
+ // delivered to the object's own receive right, identified by local_port.
|
|
+ mach_port_t port = request->msgh_local_port;
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
+ IOObject* object = lookup(port);
|
|
+ if (!object)
|
|
+ return;
|
|
+ mach_port_status_t status;
|
|
+ mach_msg_type_number_t count = MACH_PORT_RECEIVE_STATUS_COUNT;
|
|
+ if (mach_port_get_attributes(mach_task_self(), port, MACH_PORT_RECEIVE_STATUS,
|
|
+ (mach_port_info_t) &status, &count) != KERN_SUCCESS)
|
|
+ return;
|
|
+ if (status.mps_srights)
|
|
+ {
|
|
+ // A new send right was made after this notification was queued.
|
|
+ mach_port_t old = MACH_PORT_NULL;
|
|
+ mach_port_request_notification(mach_task_self(), port, MACH_NOTIFY_NO_SENDERS,
|
|
+ status.mps_mscount, port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);
|
|
+ if (MACH_PORT_VALID(old))
|
|
+ mach_port_deallocate(mach_task_self(), old);
|
|
+ return;
|
|
+ }
|
|
delete object;
|
|
- else
|
|
- os_log_error(OS_LOG_DEFAULT, "Received MACH_NOTIFY_NO_SENDERS for port %d, which matches no objects", request->msgh_remote_port);
|
|
+ });
|
|
|
|
Reply->Head.msgh_bits = 0;
|
|
Reply->Head.msgh_remote_port = MACH_PORT_NULL;
|
|
diff --git a/src/IOPMrootDomain.h b/src/IOPMrootDomain.h
|
|
new file mode 100644
|
|
index 0000000..9f9fc95
|
|
--- /dev/null
|
|
+++ b/src/IOPMrootDomain.h
|
|
@@ -0,0 +1,89 @@
|
|
+/*
|
|
+ This file is part of Darling.
|
|
+
|
|
+ Copyright (C) 2026 Darling developers
|
|
+
|
|
+ Darling is free software: you can redistribute it and/or modify
|
|
+ it under the terms of the GNU General Public License as published by
|
|
+ the Free Software Foundation, either version 3 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ Darling is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU General Public License
|
|
+ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
+*/
|
|
+
|
|
+#ifndef IOKITD_IOPMROOTDOMAIN_H
|
|
+#define IOKITD_IOPMROOTDOMAIN_H
|
|
+
|
|
+#include "IOUserClient.h"
|
|
+#include "ServiceRegistry.h"
|
|
+#include <map>
|
|
+
|
|
+struct iokitd_logind;
|
|
+
|
|
+// The power management root (IOPower:/IOPowerConnection/IOPMrootDomain).
|
|
+// IORegisterForSystemPower() opens it and registers for
|
|
+// kIOAppPowerStateInterest; host suspend and resume, as announced by
|
|
+// systemd-logind, are passed on as kIOMessageSystemWillSleep (which clients
|
|
+// acknowledge with IOAllowPowerChange()), kIOMessageSystemWillPowerOn and
|
|
+// kIOMessageSystemHasPoweredOn.
|
|
+class IOPMrootDomain : public IOService
|
|
+{
|
|
+public:
|
|
+ static IOPMrootDomain* instance();
|
|
+ static void registerSelf(ServiceRegistry* targetServiceRegistry);
|
|
+
|
|
+ const char* className() const override;
|
|
+ NSDictionary* matchingDictionary() override;
|
|
+ NSDictionary* getProperties() override;
|
|
+ bool conformsTo(const char* className) override;
|
|
+ IOReturn newUserClient(uint32_t type, IOUserClient** client) override;
|
|
+ bool acceptsInterest(const char* type) override;
|
|
+
|
|
+ // IOAllowPowerChange() / IOCancelPowerChange()
|
|
+ IOReturn allowPowerChange(uint64_t notificationID);
|
|
+ IOReturn cancelPowerChange(uint64_t notificationID);
|
|
+
|
|
+ // Follows host sleep through logind, without blocking the caller.
|
|
+ void startHostSleepMonitor();
|
|
+
|
|
+ // Main queue
|
|
+ void systemWillSleep(uint64_t cycle);
|
|
+ void systemDidWake(uint64_t cycle);
|
|
+protected:
|
|
+ void interestRemoved(IOInterestNotification* notification) override;
|
|
+private:
|
|
+ IOPMrootDomain() = default;
|
|
+ uint32_t nextNotificationID(uint16_t index);
|
|
+ void tellApps(uint32_t messageType);
|
|
+ void checkSleepReady();
|
|
+
|
|
+ struct iokitd_logind* m_logind = nullptr;
|
|
+ bool m_sleeping = false;
|
|
+ bool m_sleepReadySent = false;
|
|
+ uint64_t m_cycle = 0;
|
|
+ uint16_t m_serial = 0;
|
|
+ // Outstanding kIOMessageSystemWillSleep acknowledgements by notification ID
|
|
+ std::map<uint32_t, IOInterestNotification*> m_pendingAcks;
|
|
+};
|
|
+
|
|
+// The connection returned by IOServiceOpen() on the root domain
|
|
+class RootDomainUserClient : public IOUserClient
|
|
+{
|
|
+public:
|
|
+ RootDomainUserClient(IOPMrootDomain* owner);
|
|
+
|
|
+ const char* className() const override;
|
|
+ NSDictionary* matchingDictionary() override;
|
|
+ IOExternalMethod* getExternalMethodForIndex(UInt32 index) override;
|
|
+ IOReturn externalMethod(uint32_t selector, IOExternalMethodArguments* arguments) override;
|
|
+private:
|
|
+ IOPMrootDomain* m_owner;
|
|
+};
|
|
+
|
|
+#endif
|
|
diff --git a/src/IOPMrootDomain.mm b/src/IOPMrootDomain.mm
|
|
new file mode 100644
|
|
index 0000000..9b6d30b
|
|
--- /dev/null
|
|
+++ b/src/IOPMrootDomain.mm
|
|
@@ -0,0 +1,342 @@
|
|
+/*
|
|
+ This file is part of Darling.
|
|
+
|
|
+ Copyright (C) 2026 Darling developers
|
|
+
|
|
+ Darling is free software: you can redistribute it and/or modify
|
|
+ it under the terms of the GNU General Public License as published by
|
|
+ the Free Software Foundation, either version 3 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ Darling is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU General Public License
|
|
+ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
+*/
|
|
+
|
|
+#include "IOPMrootDomain.h"
|
|
+#include "IOInterestNotification.h"
|
|
+#include "LogindSleep.h"
|
|
+#include <IOKit/IOKitKeys.h>
|
|
+#include <IOKit/IOMessage.h>
|
|
+#include <IOKit/pwr_mgt/IOPMLibDefs.h>
|
|
+#include <dispatch/dispatch.h>
|
|
+#include <elfcalls.h>
|
|
+#include <os/log.h>
|
|
+#include <cstdlib>
|
|
+#include <cstring>
|
|
+#import <Foundation/NSString.h>
|
|
+
|
|
+extern "C" struct elf_calls* _elfcalls;
|
|
+
|
|
+// The IOPower plane parent of the root domain, as on macOS
|
|
+class IOPowerConnection : public IORegistryEntry
|
|
+{
|
|
+public:
|
|
+ const char* className() const override { return "IOPowerConnection"; }
|
|
+};
|
|
+
|
|
+IOPMrootDomain* IOPMrootDomain::instance()
|
|
+{
|
|
+ static IOPMrootDomain* domain = new IOPMrootDomain;
|
|
+ return domain;
|
|
+}
|
|
+
|
|
+void IOPMrootDomain::registerSelf(ServiceRegistry* targetServiceRegistry)
|
|
+{
|
|
+ IOPMrootDomain* domain = instance();
|
|
+ static IOPowerConnection* connection = new IOPowerConnection;
|
|
+
|
|
+ targetServiceRegistry->registerService(domain);
|
|
+ domain->registerInPlane(kIOServicePlane, "IOPMrootDomain", IORegistryEntry::root());
|
|
+ connection->registerInPlane(kIOPowerPlane, "IOPowerConnection", IORegistryEntry::root());
|
|
+ domain->registerInPlane(kIOPowerPlane, "IOPMrootDomain", connection);
|
|
+}
|
|
+
|
|
+const char* IOPMrootDomain::className() const
|
|
+{
|
|
+ return "IOPMrootDomain";
|
|
+}
|
|
+
|
|
+NSDictionary* IOPMrootDomain::matchingDictionary()
|
|
+{
|
|
+ return @{
|
|
+ @"IOProviderClass": @"IOPMrootDomain",
|
|
+ @"IOClass": @"IOPMrootDomain",
|
|
+ };
|
|
+}
|
|
+
|
|
+NSDictionary* IOPMrootDomain::getProperties()
|
|
+{
|
|
+ return @{
|
|
+ @"IOClass": @"IOPMrootDomain",
|
|
+ @"IOUserClientClass": @"RootDomainUserClient",
|
|
+ };
|
|
+}
|
|
+
|
|
+bool IOPMrootDomain::conformsTo(const char* className)
|
|
+{
|
|
+ if (std::strcmp(className, "IOPMrootDomain") == 0)
|
|
+ return true;
|
|
+ return IOService::conformsTo(className);
|
|
+}
|
|
+
|
|
+IOReturn IOPMrootDomain::newUserClient(uint32_t type, IOUserClient** client)
|
|
+{
|
|
+ try
|
|
+ {
|
|
+ *client = new RootDomainUserClient(this);
|
|
+ return kIOReturnSuccess;
|
|
+ }
|
|
+ catch (const std::exception& e)
|
|
+ {
|
|
+ os_log_error(OS_LOG_DEFAULT, "iokitd: cannot open IOPMrootDomain: %s", e.what());
|
|
+ *client = nullptr;
|
|
+ return kIOReturnNoResources;
|
|
+ }
|
|
+}
|
|
+
|
|
+bool IOPMrootDomain::acceptsInterest(const char* type)
|
|
+{
|
|
+ // General interest messages (clamshell, thermal...) are never sent here,
|
|
+ // but registering for them is valid.
|
|
+ return std::strcmp(type, kIOAppPowerStateInterest) == 0
|
|
+ || std::strcmp(type, kIOGeneralInterest) == 0;
|
|
+}
|
|
+
|
|
+// Like xnu: the change serial number in the upper half, the client in the lower one.
|
|
+uint32_t IOPMrootDomain::nextNotificationID(uint16_t index)
|
|
+{
|
|
+ return (uint32_t(m_serial) << 16) | index;
|
|
+}
|
|
+
|
|
+void IOPMrootDomain::tellApps(uint32_t messageType)
|
|
+{
|
|
+ uint16_t index = 0;
|
|
+
|
|
+ m_serial++;
|
|
+ for (IOInterestNotification* n : interests(kIOAppPowerStateInterest))
|
|
+ {
|
|
+ uint32_t id = messageType == kIOMessageSystemWillSleep ? nextNotificationID(++index) : 0;
|
|
+
|
|
+ if (n->deliver(messageType, id) == KERN_SUCCESS && messageType == kIOMessageSystemWillSleep)
|
|
+ m_pendingAcks[id] = n;
|
|
+ }
|
|
+}
|
|
+
|
|
+void IOPMrootDomain::systemWillSleep(uint64_t cycle)
|
|
+{
|
|
+ os_log(OS_LOG_DEFAULT, "iokitd: host is going to sleep, notifying applications");
|
|
+
|
|
+ m_cycle = cycle;
|
|
+ m_sleeping = true;
|
|
+ m_sleepReadySent = false;
|
|
+ m_pendingAcks.clear();
|
|
+
|
|
+ // kIOMessageCanSystemSleep is not sent: logind only announces a suspend
|
|
+ // that has already been decided, so there is nothing applications could veto.
|
|
+ tellApps(kIOMessageSystemWillSleep);
|
|
+ checkSleepReady();
|
|
+}
|
|
+
|
|
+void IOPMrootDomain::systemDidWake(uint64_t cycle)
|
|
+{
|
|
+ os_log(OS_LOG_DEFAULT, "iokitd: host has woken up, notifying applications");
|
|
+
|
|
+ m_sleeping = false;
|
|
+ m_pendingAcks.clear();
|
|
+
|
|
+ tellApps(kIOMessageSystemWillPowerOn);
|
|
+ tellApps(kIOMessageSystemHasPoweredOn);
|
|
+}
|
|
+
|
|
+void IOPMrootDomain::checkSleepReady()
|
|
+{
|
|
+ if (!m_sleeping || m_sleepReadySent || !m_pendingAcks.empty() || !m_logind)
|
|
+ return;
|
|
+
|
|
+ m_sleepReadySent = true;
|
|
+ iokitd_logind_sleep_ready(m_logind, m_cycle);
|
|
+}
|
|
+
|
|
+IOReturn IOPMrootDomain::allowPowerChange(uint64_t notificationID)
|
|
+{
|
|
+ // Unknown or late IDs are ignored, as on macOS.
|
|
+ m_pendingAcks.erase(uint32_t(notificationID));
|
|
+ checkSleepReady();
|
|
+ return kIOReturnSuccess;
|
|
+}
|
|
+
|
|
+IOReturn IOPMrootDomain::cancelPowerChange(uint64_t notificationID)
|
|
+{
|
|
+ // Only an idle sleep query (kIOMessageCanSystemSleep) can be cancelled,
|
|
+ // and those are never sent. Treat it as an acknowledgement, like macOS
|
|
+ // does for kIOMessageSystemWillSleep.
|
|
+ return allowPowerChange(notificationID);
|
|
+}
|
|
+
|
|
+void IOPMrootDomain::interestRemoved(IOInterestNotification* notification)
|
|
+{
|
|
+ for (auto it = m_pendingAcks.begin(); it != m_pendingAcks.end(); )
|
|
+ {
|
|
+ if (it->second == notification)
|
|
+ it = m_pendingAcks.erase(it);
|
|
+ else
|
|
+ ++it;
|
|
+ }
|
|
+ checkSleepReady();
|
|
+}
|
|
+
|
|
+// Host libsystemd, loaded through the ELF loader. iokitd shares the host's
|
|
+// user and network, so the system bus socket is reachable directly.
|
|
+
|
|
+static void logindLog(const char* message)
|
|
+{
|
|
+ os_log(OS_LOG_DEFAULT, "iokitd: %{public}s", message);
|
|
+}
|
|
+
|
|
+static bool loadLogindApi(struct iokitd_logind_api* api)
|
|
+{
|
|
+ void *systemd, *libc;
|
|
+
|
|
+ if (_elfcalls == nullptr)
|
|
+ return false;
|
|
+ systemd = _elfcalls->dlopen("libsystemd.so.0");
|
|
+ if (systemd == nullptr)
|
|
+ return false;
|
|
+ libc = _elfcalls->dlopen("libc.so.6");
|
|
+ if (libc == nullptr)
|
|
+ {
|
|
+ _elfcalls->dlclose(systemd);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ std::memset(api, 0, sizeof(*api));
|
|
+#define BIND(lib, field) \
|
|
+ if ((*(void**) &api->field = _elfcalls->dlsym(lib, #field)) == nullptr) \
|
|
+ goto fail;
|
|
+ BIND(systemd, sd_bus_new);
|
|
+ BIND(systemd, sd_bus_set_address);
|
|
+ BIND(systemd, sd_bus_set_bus_client);
|
|
+ BIND(systemd, sd_bus_start);
|
|
+ BIND(systemd, sd_bus_set_method_call_timeout);
|
|
+ BIND(systemd, sd_bus_flush_close_unref);
|
|
+ BIND(systemd, sd_bus_match_signal);
|
|
+ BIND(systemd, sd_bus_call_method);
|
|
+ BIND(systemd, sd_bus_get_property_trivial);
|
|
+ BIND(systemd, sd_bus_message_read);
|
|
+ BIND(systemd, sd_bus_message_unref);
|
|
+ BIND(systemd, sd_bus_slot_unref);
|
|
+ BIND(systemd, sd_bus_error_free);
|
|
+ BIND(systemd, sd_bus_process);
|
|
+ BIND(systemd, sd_bus_wait);
|
|
+ // The inhibitor is a host descriptor; use the host's calls on it.
|
|
+ BIND(libc, fcntl);
|
|
+ BIND(libc, close);
|
|
+#undef BIND
|
|
+ api->getenv = getenv;
|
|
+ api->log = logindLog;
|
|
+ // The monitor and both libraries live until process exit, which closes
|
|
+ // the host bus and inhibitor descriptors. stop() is used by native tests.
|
|
+ return true;
|
|
+
|
|
+fail:
|
|
+ _elfcalls->dlclose(libc);
|
|
+ _elfcalls->dlclose(systemd);
|
|
+ return false;
|
|
+}
|
|
+
|
|
+const struct iokitd_logind_api* iokitd_host_power_api(void)
|
|
+{
|
|
+ static struct iokitd_logind_api api;
|
|
+ static bool available;
|
|
+ static dispatch_once_t once;
|
|
+ dispatch_once(&once, ^{ available = loadLogindApi(&api); });
|
|
+ return available ? &api : nullptr;
|
|
+}
|
|
+
|
|
+static void logindWillSleep(void* context, uint64_t cycle)
|
|
+{
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
+ IOPMrootDomain::instance()->systemWillSleep(cycle);
|
|
+ });
|
|
+}
|
|
+
|
|
+static void logindDidWake(void* context, uint64_t cycle)
|
|
+{
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
+ IOPMrootDomain::instance()->systemDidWake(cycle);
|
|
+ });
|
|
+}
|
|
+
|
|
+void IOPMrootDomain::startHostSleepMonitor()
|
|
+{
|
|
+ // Connecting and taking the inhibitor are blocking D-Bus calls.
|
|
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
+ const struct iokitd_logind_api* api = iokitd_host_power_api();
|
|
+ static const struct iokitd_logind_callbacks callbacks = { logindWillSleep, logindDidWake };
|
|
+ struct iokitd_logind* logind = nullptr;
|
|
+
|
|
+ if (!api)
|
|
+ {
|
|
+ os_log(OS_LOG_DEFAULT, "iokitd: host libsystemd not available, no sleep/wake notifications");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ int r = iokitd_logind_start(api, &callbacks, nullptr, &logind);
|
|
+ if (r < 0)
|
|
+ {
|
|
+ os_log(OS_LOG_DEFAULT, "iokitd: logind sleep monitor not started (%d), no sleep/wake notifications", r);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
+ IOPMrootDomain* domain = IOPMrootDomain::instance();
|
|
+ domain->m_logind = logind;
|
|
+ // A suspend may have been announced before we got here.
|
|
+ domain->checkSleepReady();
|
|
+ });
|
|
+ });
|
|
+}
|
|
+
|
|
+RootDomainUserClient::RootDomainUserClient(IOPMrootDomain* owner)
|
|
+: m_owner(owner)
|
|
+{
|
|
+}
|
|
+
|
|
+const char* RootDomainUserClient::className() const
|
|
+{
|
|
+ return "RootDomainUserClient";
|
|
+}
|
|
+
|
|
+NSDictionary* RootDomainUserClient::matchingDictionary()
|
|
+{
|
|
+ return @{};
|
|
+}
|
|
+
|
|
+IOExternalMethod* RootDomainUserClient::getExternalMethodForIndex(UInt32 index)
|
|
+{
|
|
+ return nullptr;
|
|
+}
|
|
+
|
|
+IOReturn RootDomainUserClient::externalMethod(uint32_t selector, IOExternalMethodArguments* arguments)
|
|
+{
|
|
+ arguments->scalarOutputCount = 0;
|
|
+ arguments->structureOutputSize = 0;
|
|
+
|
|
+ switch (selector)
|
|
+ {
|
|
+ case kPMAllowPowerChange:
|
|
+ case kPMCancelPowerChange:
|
|
+ if (arguments->scalarInputCount != 1)
|
|
+ return kIOReturnBadArgument;
|
|
+ if (selector == kPMAllowPowerChange)
|
|
+ return m_owner->allowPowerChange(arguments->scalarInput[0]);
|
|
+ return m_owner->cancelPowerChange(arguments->scalarInput[0]);
|
|
+ default:
|
|
+ return kIOReturnUnsupported;
|
|
+ }
|
|
+}
|
|
diff --git a/src/IOService.h b/src/IOService.h
|
|
index ab8f5c3..08d34e7 100644
|
|
--- a/src/IOService.h
|
|
+++ b/src/IOService.h
|
|
@@ -23,6 +23,11 @@
|
|
#include "IORegistryEntry.h"
|
|
#include <IOKit/IOKitLib.h>
|
|
#include <IOKit/IOKitLibPrivate.h>
|
|
+#include <list>
|
|
+#include <vector>
|
|
+
|
|
+class IOInterestNotification;
|
|
+class IOUserClient;
|
|
|
|
class IOService : public IORegistryEntry
|
|
{
|
|
@@ -32,10 +37,23 @@ public:
|
|
bool matches(NSDictionary* dict);
|
|
bool conformsTo(const char* className) override;
|
|
|
|
+ // IOServiceOpen(). On success *client holds the reference handed to the caller.
|
|
+ virtual IOReturn newUserClient(uint32_t type, IOUserClient** client);
|
|
+
|
|
+ // IOServiceAddInterestNotification(). Services opt in per interest type.
|
|
+ virtual bool acceptsInterest(const char* type);
|
|
+ void addInterest(IOInterestNotification* notification);
|
|
+ void removeInterest(IOInterestNotification* notification);
|
|
+ std::vector<IOInterestNotification*> interests(const char* type) const;
|
|
+
|
|
uint32_t busyState() const { return m_busyState; }
|
|
uint64_t accumulatedBusyTime() const { return m_accumulatedBusyTime; }
|
|
uint64_t state() const { return m_state; }
|
|
protected:
|
|
+ // Called after a client released its interest notification
|
|
+ virtual void interestRemoved(IOInterestNotification* notification) {}
|
|
+
|
|
+ std::list<IOInterestNotification*> m_interests;
|
|
uint32_t m_busyState = 0;
|
|
uint64_t m_accumulatedBusyTime = 0;
|
|
uint64_t m_state = kIOServiceRegisteredState | kIOServiceMatchedState;
|
|
diff --git a/src/IOService.mm b/src/IOService.mm
|
|
index 0316b30..25904fa 100644
|
|
--- a/src/IOService.mm
|
|
+++ b/src/IOService.mm
|
|
@@ -18,6 +18,11 @@
|
|
*/
|
|
|
|
#include "IOService.h"
|
|
+#include "IOInterestNotification.h"
|
|
+#include "IOUserClient.h"
|
|
+#include <algorithm>
|
|
+#include <cstring>
|
|
+#include <os/log.h>
|
|
#import <Foundation/NSArray.h>
|
|
|
|
extern "C" {
|
|
@@ -71,6 +76,92 @@ bool IOService::conformsTo(const char* className)
|
|
return IOObject::conformsTo(className);
|
|
}
|
|
|
|
+IOReturn IOService::newUserClient(uint32_t type, IOUserClient** client)
|
|
+{
|
|
+ *client = nullptr;
|
|
+ return kIOReturnUnsupported;
|
|
+}
|
|
+
|
|
+bool IOService::acceptsInterest(const char* type)
|
|
+{
|
|
+ return false;
|
|
+}
|
|
+
|
|
+void IOService::addInterest(IOInterestNotification* notification)
|
|
+{
|
|
+ m_interests.push_back(notification);
|
|
+}
|
|
+
|
|
+void IOService::removeInterest(IOInterestNotification* notification)
|
|
+{
|
|
+ m_interests.remove(notification);
|
|
+ interestRemoved(notification);
|
|
+}
|
|
+
|
|
+std::vector<IOInterestNotification*> IOService::interests(const char* type) const
|
|
+{
|
|
+ std::vector<IOInterestNotification*> result;
|
|
+ for (IOInterestNotification* notification : m_interests)
|
|
+ {
|
|
+ if (notification->interestType() == type)
|
|
+ result.push_back(notification);
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+kern_return_t is_io_service_open_extended
|
|
+(
|
|
+ mach_port_t service,
|
|
+ task_t owningTask,
|
|
+ uint32_t connect_type,
|
|
+ NDR_record_t ndr,
|
|
+ io_buf_ptr_t properties,
|
|
+ mach_msg_type_number_t propertiesCnt,
|
|
+ kern_return_t *result,
|
|
+ mach_port_t *connection
|
|
+)
|
|
+{
|
|
+ *connection = MACH_PORT_NULL;
|
|
+
|
|
+ // The rights and memory in the request are ours once we return success.
|
|
+ if (properties)
|
|
+ vm_deallocate(mach_task_self(), (vm_address_t) properties, propertiesCnt);
|
|
+ if (MACH_PORT_VALID(owningTask))
|
|
+ mach_port_deallocate(mach_task_self(), owningTask);
|
|
+
|
|
+ IOService* s = dynamic_cast<IOService*>(IOObject::lookup(service));
|
|
+ if (!s)
|
|
+ {
|
|
+ *result = kIOReturnBadArgument;
|
|
+ return KERN_SUCCESS;
|
|
+ }
|
|
+
|
|
+ IOUserClient* client = nullptr;
|
|
+ *result = s->newUserClient(connect_type, &client);
|
|
+
|
|
+ if (*result == kIOReturnSuccess && client)
|
|
+ {
|
|
+ *connection = client->port();
|
|
+ // The reply carries a copy of the send right; drop ours afterwards.
|
|
+ client->releaseLater();
|
|
+ }
|
|
+ else if (*result == kIOReturnSuccess)
|
|
+ *result = kIOReturnUnsupported;
|
|
+
|
|
+ return KERN_SUCCESS;
|
|
+}
|
|
+
|
|
+kern_return_t is_io_service_close
|
|
+(
|
|
+ mach_port_t connection
|
|
+)
|
|
+{
|
|
+ // The connection goes away once the caller releases its last reference.
|
|
+ if (!dynamic_cast<IOUserClient*>(IOObject::lookup(connection)))
|
|
+ return kIOReturnBadArgument;
|
|
+ return kIOReturnSuccess;
|
|
+}
|
|
+
|
|
kern_return_t is_io_service_get_state
|
|
(
|
|
mach_port_t service,
|
|
diff --git a/src/IOUserClient.h b/src/IOUserClient.h
|
|
index 82c10db..adeb966 100644
|
|
--- a/src/IOUserClient.h
|
|
+++ b/src/IOUserClient.h
|
|
@@ -28,7 +28,8 @@
|
|
class IOUserClient : public IOService
|
|
{
|
|
public:
|
|
- IOReturn externalMethod(uint32_t selector, IOExternalMethodArguments *arguments);
|
|
+ // The default dispatches through getExternalMethodForIndex()
|
|
+ virtual IOReturn externalMethod(uint32_t selector, IOExternalMethodArguments *arguments);
|
|
IOExternalMethod *getTargetAndMethodForIndex(IOService **targetP, UInt32 index);
|
|
virtual IOExternalMethod *getExternalMethodForIndex(UInt32 index) = 0;
|
|
};
|
|
diff --git a/src/LogindSleep.c b/src/LogindSleep.c
|
|
new file mode 100644
|
|
index 0000000..1d6aa79
|
|
--- /dev/null
|
|
+++ b/src/LogindSleep.c
|
|
@@ -0,0 +1,455 @@
|
|
+/*
|
|
+ This file is part of Darling.
|
|
+
|
|
+ Copyright (C) 2026 Darling developers
|
|
+
|
|
+ Darling is free software: you can redistribute it and/or modify
|
|
+ it under the terms of the GNU General Public License as published by
|
|
+ the Free Software Foundation, either version 3 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ Darling is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU General Public License
|
|
+ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
+*/
|
|
+
|
|
+#include "LogindSleep.h"
|
|
+#include <pthread.h>
|
|
+#include <stdarg.h>
|
|
+#include <stdio.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <time.h>
|
|
+
|
|
+#define LOGIND_NAME "org.freedesktop.login1"
|
|
+#define LOGIND_PATH "/org/freedesktop/login1"
|
|
+#define LOGIND_MANAGER "org.freedesktop.login1.Manager"
|
|
+
|
|
+#define USEC_PER_SEC 1000000ULL
|
|
+/* logind's default InhibitDelayMaxSec */
|
|
+#define DEFAULT_LOGIND_DELAY_USEC (5 * USEC_PER_SEC)
|
|
+/* What macOS gives applications to answer kIOMessageSystemWillSleep */
|
|
+#define MAX_ACK_TIMEOUT_USEC (30 * USEC_PER_SEC)
|
|
+#define MIN_ACK_TIMEOUT_USEC (USEC_PER_SEC / 10)
|
|
+/* Upper bound on how long the bus thread sleeps before checking deadlines and stop */
|
|
+#define POLL_INTERVAL_USEC (200 * 1000ULL)
|
|
+#define CALL_TIMEOUT_USEC (5 * USEC_PER_SEC)
|
|
+
|
|
+struct iokitd_logind
|
|
+{
|
|
+ struct iokitd_logind_api api;
|
|
+ struct iokitd_logind_callbacks callbacks;
|
|
+ void* context;
|
|
+
|
|
+ void* bus;
|
|
+ void* slot;
|
|
+ void* owner_slot;
|
|
+ char* address;
|
|
+ pthread_t thread;
|
|
+ int thread_started;
|
|
+ uint64_t ack_timeout_usec;
|
|
+
|
|
+ /* Shared with iokitd_logind_sleep_ready() */
|
|
+ pthread_mutex_t lock;
|
|
+ int inhibitor_fd;
|
|
+ int sleeping;
|
|
+ uint64_t cycle;
|
|
+ uint64_t deadline_usec;
|
|
+ int stop;
|
|
+
|
|
+ /* Bus thread only */
|
|
+ int want_inhibitor;
|
|
+};
|
|
+
|
|
+static uint64_t now_usec(void)
|
|
+{
|
|
+ struct timespec ts;
|
|
+ clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
+ return (uint64_t) ts.tv_sec * USEC_PER_SEC + (uint64_t) ts.tv_nsec / 1000;
|
|
+}
|
|
+
|
|
+static void log_message(struct iokitd_logind* l, const char* format, ...)
|
|
+ __attribute__((format(printf, 2, 3)));
|
|
+
|
|
+static void log_message(struct iokitd_logind* l, const char* format, ...)
|
|
+{
|
|
+ char buffer[256];
|
|
+ va_list ap;
|
|
+
|
|
+ if (l->api.log == NULL)
|
|
+ return;
|
|
+ va_start(ap, format);
|
|
+ vsnprintf(buffer, sizeof(buffer), format, ap);
|
|
+ va_end(ap);
|
|
+ l->api.log(buffer);
|
|
+}
|
|
+
|
|
+static int api_complete(const struct iokitd_logind_api* api)
|
|
+{
|
|
+ return api != NULL && api->sd_bus_new && api->sd_bus_set_address && api->sd_bus_set_bus_client
|
|
+ && api->sd_bus_start && api->sd_bus_set_method_call_timeout && api->sd_bus_flush_close_unref && api->sd_bus_match_signal
|
|
+ && api->sd_bus_call_method && api->sd_bus_get_property_trivial && api->sd_bus_message_read
|
|
+ && api->sd_bus_message_unref && api->sd_bus_slot_unref && api->sd_bus_error_free
|
|
+ && api->sd_bus_process && api->sd_bus_wait && api->fcntl && api->close && api->getenv;
|
|
+}
|
|
+
|
|
+/* Must hold l->lock. */
|
|
+static void close_inhibitor_locked(struct iokitd_logind* l)
|
|
+{
|
|
+ if (l->inhibitor_fd >= 0)
|
|
+ {
|
|
+ l->api.close(l->inhibitor_fd);
|
|
+ l->inhibitor_fd = -1;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Bus thread (or start, before the thread exists). */
|
|
+static void take_inhibitor(struct iokitd_logind* l)
|
|
+{
|
|
+ struct iokitd_bus_error error = { NULL, NULL, 0 };
|
|
+ void* reply = NULL;
|
|
+ int fd = -1, own = -1, r;
|
|
+
|
|
+ r = l->api.sd_bus_call_method(l->bus, LOGIND_NAME, LOGIND_PATH, LOGIND_MANAGER, "Inhibit",
|
|
+ &error, &reply, "ssss", "sleep", "Darling",
|
|
+ "Notify macOS applications before the system sleeps", "delay");
|
|
+ if (r >= 0)
|
|
+ r = l->api.sd_bus_message_read(reply, "h", &fd);
|
|
+ /* The descriptor belongs to the reply message; keep our own copy. */
|
|
+ if (r >= 0)
|
|
+ {
|
|
+ own = l->api.fcntl(fd, IOKITD_LINUX_F_DUPFD_CLOEXEC, 3);
|
|
+ if (own < 0)
|
|
+ r = -IOKITD_LINUX_ENOMEM;
|
|
+ }
|
|
+ if (r < 0)
|
|
+ log_message(l, "logind: cannot take a sleep delay lock: %s (%d)",
|
|
+ error.message ? error.message : "no reply", r);
|
|
+
|
|
+ if (reply != NULL)
|
|
+ l->api.sd_bus_message_unref(reply);
|
|
+ l->api.sd_bus_error_free(&error);
|
|
+
|
|
+ if (own >= 0)
|
|
+ {
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ close_inhibitor_locked(l);
|
|
+ l->inhibitor_fd = own;
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int on_prepare_for_sleep(void* m, void* userdata, struct iokitd_bus_error* ret_error)
|
|
+{
|
|
+ struct iokitd_logind* l = userdata;
|
|
+ int start = 0;
|
|
+ uint64_t cycle;
|
|
+
|
|
+ (void) ret_error;
|
|
+ if (l->api.sd_bus_message_read(m, "b", &start) < 0)
|
|
+ return 0;
|
|
+
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ if (start)
|
|
+ {
|
|
+ l->cycle++;
|
|
+ l->sleeping = 1;
|
|
+ l->deadline_usec = now_usec() + l->ack_timeout_usec;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ l->sleeping = 0;
|
|
+ /* If logind suspended without waiting for us, the old lock is still valid. */
|
|
+ l->want_inhibitor = l->inhibitor_fd < 0;
|
|
+ }
|
|
+ cycle = l->cycle;
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+
|
|
+ if (start)
|
|
+ {
|
|
+ if (l->callbacks.will_sleep)
|
|
+ l->callbacks.will_sleep(l->context, cycle);
|
|
+ }
|
|
+ else if (l->callbacks.did_wake)
|
|
+ l->callbacks.did_wake(l->context, cycle);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* logind may appear after iokitd starts, or restart while we are awake. */
|
|
+static int on_logind_owner_changed(void* m, void* userdata, struct iokitd_bus_error* error)
|
|
+{
|
|
+ struct iokitd_logind* l = userdata;
|
|
+ const char *name, *old_owner, *new_owner;
|
|
+ (void) error;
|
|
+ if (l->api.sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner) < 0
|
|
+ || strcmp(name, LOGIND_NAME) != 0)
|
|
+ return 0;
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ close_inhibitor_locked(l);
|
|
+ l->sleeping = 0;
|
|
+ l->want_inhibitor = new_owner[0] != '\0';
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void close_bus(struct iokitd_logind* l)
|
|
+{
|
|
+ if (l->slot) l->api.sd_bus_slot_unref(l->slot);
|
|
+ if (l->owner_slot) l->api.sd_bus_slot_unref(l->owner_slot);
|
|
+ if (l->bus) l->api.sd_bus_flush_close_unref(l->bus);
|
|
+ l->slot = l->owner_slot = l->bus = NULL;
|
|
+}
|
|
+
|
|
+static int connect_bus(struct iokitd_logind* l)
|
|
+{
|
|
+ int r;
|
|
+ r = l->api.sd_bus_new(&l->bus);
|
|
+ if (r >= 0)
|
|
+ r = l->api.sd_bus_set_address(l->bus, l->address);
|
|
+ if (r >= 0)
|
|
+ r = l->api.sd_bus_set_bus_client(l->bus, 1);
|
|
+ if (r >= 0)
|
|
+ r = l->api.sd_bus_set_method_call_timeout(l->bus, CALL_TIMEOUT_USEC);
|
|
+ if (r >= 0)
|
|
+ r = l->api.sd_bus_start(l->bus);
|
|
+ /* Subscribe before taking the lock so no announcement is missed. */
|
|
+ if (r >= 0)
|
|
+ r = l->api.sd_bus_match_signal(l->bus, &l->slot, LOGIND_NAME, LOGIND_PATH, LOGIND_MANAGER,
|
|
+ "PrepareForSleep", on_prepare_for_sleep, l);
|
|
+ if (r >= 0)
|
|
+ r = l->api.sd_bus_match_signal(l->bus, &l->owner_slot, "org.freedesktop.DBus",
|
|
+ "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged",
|
|
+ on_logind_owner_changed, l);
|
|
+ if (r < 0)
|
|
+ close_bus(l);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static void connection_lost(struct iokitd_logind* l)
|
|
+{
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ close_inhibitor_locked(l);
|
|
+ l->sleeping = 0;
|
|
+ l->want_inhibitor = 0;
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+ close_bus(l);
|
|
+}
|
|
+
|
|
+static void* bus_thread(void* arg)
|
|
+{
|
|
+ struct iokitd_logind* l = arg;
|
|
+ uint64_t next_reconnect = 0, backoff = USEC_PER_SEC;
|
|
+
|
|
+ for (;;)
|
|
+ {
|
|
+ uint64_t wait = POLL_INTERVAL_USEC, now;
|
|
+ int r;
|
|
+
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ if (l->stop)
|
|
+ {
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+ break;
|
|
+ }
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+
|
|
+ if (!l->bus)
|
|
+ {
|
|
+ now = now_usec();
|
|
+ if (now < next_reconnect)
|
|
+ {
|
|
+ struct timespec pause = {0, POLL_INTERVAL_USEC * 1000};
|
|
+ nanosleep(&pause, NULL);
|
|
+ continue;
|
|
+ }
|
|
+ r = connect_bus(l);
|
|
+ if (r < 0)
|
|
+ {
|
|
+ log_message(l, "logind: reconnect failed (%d), retrying", r);
|
|
+ next_reconnect = now_usec() + backoff;
|
|
+ backoff = backoff < 15 * USEC_PER_SEC ? backoff * 2 : 30 * USEC_PER_SEC;
|
|
+ continue;
|
|
+ }
|
|
+ backoff = USEC_PER_SEC;
|
|
+ take_inhibitor(l);
|
|
+ log_message(l, "logind: system bus reconnected");
|
|
+ }
|
|
+
|
|
+ r = l->api.sd_bus_process(l->bus, NULL);
|
|
+ if (r < 0)
|
|
+ {
|
|
+ log_message(l, "logind: system bus connection lost (%d), reconnecting", r);
|
|
+ connection_lost(l);
|
|
+ next_reconnect = 0;
|
|
+ continue;
|
|
+ }
|
|
+ if (r > 0)
|
|
+ continue;
|
|
+
|
|
+ if (l->want_inhibitor)
|
|
+ {
|
|
+ l->want_inhibitor = 0;
|
|
+ take_inhibitor(l);
|
|
+ }
|
|
+
|
|
+ now = now_usec();
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ if (l->sleeping && l->inhibitor_fd >= 0)
|
|
+ {
|
|
+ if (now >= l->deadline_usec)
|
|
+ {
|
|
+ log_message(l, "logind: applications did not acknowledge sleep in time");
|
|
+ close_inhibitor_locked(l);
|
|
+ }
|
|
+ else if (l->deadline_usec - now < wait)
|
|
+ wait = l->deadline_usec - now;
|
|
+ }
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+
|
|
+ r = l->api.sd_bus_wait(l->bus, wait);
|
|
+ if (r < 0 && r != -IOKITD_LINUX_EINTR)
|
|
+ {
|
|
+ log_message(l, "logind: waiting on the system bus failed (%d), reconnecting", r);
|
|
+ connection_lost(l);
|
|
+ next_reconnect = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Never hold up a suspend once nobody is listening. */
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ close_inhibitor_locked(l);
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static uint64_t derive_ack_timeout(struct iokitd_logind* l)
|
|
+{
|
|
+ struct iokitd_bus_error error = { NULL, NULL, 0 };
|
|
+ uint64_t max_delay = 0, timeout;
|
|
+ int r;
|
|
+
|
|
+ if (l->api.ack_timeout_usec)
|
|
+ return l->api.ack_timeout_usec;
|
|
+
|
|
+ r = l->api.sd_bus_get_property_trivial(l->bus, LOGIND_NAME, LOGIND_PATH, LOGIND_MANAGER,
|
|
+ "InhibitDelayMaxUSec", &error, 't', &max_delay);
|
|
+ l->api.sd_bus_error_free(&error);
|
|
+ if (r < 0 || max_delay == 0)
|
|
+ max_delay = DEFAULT_LOGIND_DELAY_USEC;
|
|
+
|
|
+ timeout = max_delay > 2 * USEC_PER_SEC ? max_delay - USEC_PER_SEC : max_delay / 2;
|
|
+ if (timeout > MAX_ACK_TIMEOUT_USEC)
|
|
+ timeout = MAX_ACK_TIMEOUT_USEC;
|
|
+ if (timeout < MIN_ACK_TIMEOUT_USEC)
|
|
+ timeout = MIN_ACK_TIMEOUT_USEC;
|
|
+ return timeout;
|
|
+}
|
|
+
|
|
+static void destroy(struct iokitd_logind* l)
|
|
+{
|
|
+ close_bus(l);
|
|
+ free(l->address);
|
|
+ // No bus thread exists here (startup failure or stop has joined it).
|
|
+ close_inhibitor_locked(l);
|
|
+ pthread_mutex_destroy(&l->lock);
|
|
+ free(l);
|
|
+}
|
|
+
|
|
+int iokitd_logind_start(const struct iokitd_logind_api* api,
|
|
+ const struct iokitd_logind_callbacks* callbacks, void* context,
|
|
+ struct iokitd_logind** ret)
|
|
+{
|
|
+ struct iokitd_logind* l;
|
|
+ const char* address;
|
|
+ int r;
|
|
+
|
|
+ *ret = NULL;
|
|
+ if (!api_complete(api) || callbacks == NULL)
|
|
+ return -IOKITD_LINUX_ENOSYS;
|
|
+
|
|
+ l = calloc(1, sizeof(*l));
|
|
+ if (l == NULL)
|
|
+ return -IOKITD_LINUX_ENOMEM;
|
|
+ l->api = *api;
|
|
+ l->callbacks = *callbacks;
|
|
+ l->context = context;
|
|
+ l->inhibitor_fd = -1;
|
|
+ // Signal callbacks can run while synchronous startup calls pump the bus.
|
|
+ l->ack_timeout_usec = api->ack_timeout_usec ? api->ack_timeout_usec
|
|
+ : DEFAULT_LOGIND_DELAY_USEC - USEC_PER_SEC;
|
|
+ pthread_mutex_init(&l->lock, NULL);
|
|
+
|
|
+ address = api->getenv("DBUS_SYSTEM_BUS_ADDRESS");
|
|
+ if (address == NULL || address[0] == '\0')
|
|
+ address = IOKITD_LOGIND_DEFAULT_SYSTEM_BUS;
|
|
+
|
|
+ l->address = strdup(address);
|
|
+ if (!l->address)
|
|
+ {
|
|
+ destroy(l);
|
|
+ return -IOKITD_LINUX_ENOMEM;
|
|
+ }
|
|
+ r = connect_bus(l);
|
|
+ if (r < 0)
|
|
+ {
|
|
+ log_message(l, "logind: cannot use the system bus at %s (%d)", address, r);
|
|
+ destroy(l);
|
|
+ return r;
|
|
+ }
|
|
+
|
|
+ l->ack_timeout_usec = derive_ack_timeout(l);
|
|
+ take_inhibitor(l);
|
|
+
|
|
+ r = pthread_create(&l->thread, NULL, bus_thread, l);
|
|
+ if (r != 0)
|
|
+ {
|
|
+ destroy(l);
|
|
+ return -IOKITD_LINUX_ENOMEM;
|
|
+ }
|
|
+ l->thread_started = 1;
|
|
+ *ret = l;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void iokitd_logind_sleep_ready(struct iokitd_logind* l, uint64_t cycle)
|
|
+{
|
|
+ if (l == NULL)
|
|
+ return;
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ if (l->sleeping && l->cycle == cycle)
|
|
+ close_inhibitor_locked(l);
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+}
|
|
+
|
|
+uint64_t iokitd_logind_ack_timeout_usec(const struct iokitd_logind* l)
|
|
+{
|
|
+ return l ? l->ack_timeout_usec : 0;
|
|
+}
|
|
+
|
|
+int iokitd_logind_holds_inhibitor(struct iokitd_logind* l)
|
|
+{
|
|
+ int held;
|
|
+
|
|
+ if (l == NULL)
|
|
+ return 0;
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ held = l->inhibitor_fd >= 0;
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+ return held;
|
|
+}
|
|
+
|
|
+void iokitd_logind_stop(struct iokitd_logind* l)
|
|
+{
|
|
+ if (l == NULL)
|
|
+ return;
|
|
+ pthread_mutex_lock(&l->lock);
|
|
+ l->stop = 1;
|
|
+ pthread_mutex_unlock(&l->lock);
|
|
+ if (l->thread_started)
|
|
+ pthread_join(l->thread, NULL);
|
|
+ destroy(l);
|
|
+}
|
|
diff --git a/src/LogindSleep.h b/src/LogindSleep.h
|
|
new file mode 100644
|
|
index 0000000..a161a68
|
|
--- /dev/null
|
|
+++ b/src/LogindSleep.h
|
|
@@ -0,0 +1,148 @@
|
|
+/*
|
|
+ This file is part of Darling.
|
|
+
|
|
+ Copyright (C) 2026 Darling developers
|
|
+
|
|
+ Darling is free software: you can redistribute it and/or modify
|
|
+ it under the terms of the GNU General Public License as published by
|
|
+ the Free Software Foundation, either version 3 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ Darling is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU General Public License
|
|
+ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
+*/
|
|
+
|
|
+/*
|
|
+ * Follows host suspend/resume through systemd-logind on the system bus.
|
|
+ *
|
|
+ * At start a "delay" sleep inhibitor is taken. When logind announces a
|
|
+ * suspend (PrepareForSleep(true)), will_sleep() is called and the inhibitor
|
|
+ * is kept until iokitd_logind_sleep_ready() is called for that cycle or the
|
|
+ * acknowledgement timeout expires, whichever comes first; then it is closed
|
|
+ * so the suspend can proceed. After resume (PrepareForSleep(false)),
|
|
+ * did_wake() is called and a new inhibitor is taken.
|
|
+ *
|
|
+ * The bus is owned by one background thread; the callbacks run on it.
|
|
+ * iokitd_logind_sleep_ready() may be called from any thread.
|
|
+ *
|
|
+ * This part has no Darling dependencies: every host function comes from the
|
|
+ * table below, so it can also be built natively on Linux against libsystemd
|
|
+ * for testing. Errors are negative Linux errno values as returned by sd-bus.
|
|
+ */
|
|
+
|
|
+#ifndef IOKITD_LOGIND_SLEEP_H_
|
|
+#define IOKITD_LOGIND_SLEEP_H_
|
|
+
|
|
+#include <stdint.h>
|
|
+
|
|
+#ifdef __cplusplus
|
|
+extern "C" {
|
|
+#endif
|
|
+
|
|
+/* Linux values; the guest's numbering differs. */
|
|
+#define IOKITD_LINUX_EINVAL 22
|
|
+#define IOKITD_LINUX_ENOMEM 12
|
|
+#define IOKITD_LINUX_ENOSYS 38
|
|
+#define IOKITD_LINUX_EINTR 4
|
|
+#define IOKITD_LINUX_F_DUPFD_CLOEXEC 1030
|
|
+
|
|
+#define IOKITD_LOGIND_DEFAULT_SYSTEM_BUS "unix:path=/run/dbus/system_bus_socket"
|
|
+
|
|
+/* Layout of sd_bus_error. */
|
|
+struct iokitd_bus_error
|
|
+{
|
|
+ const char* name;
|
|
+ const char* message;
|
|
+ int _need_free;
|
|
+};
|
|
+
|
|
+typedef int (*iokitd_bus_handler)(void* m, void* userdata, struct iokitd_bus_error* ret_error);
|
|
+
|
|
+struct iokitd_logind_api
|
|
+{
|
|
+ /* libsystemd (sd-bus); bus, message and slot objects are opaque */
|
|
+ int (*sd_bus_new)(void** ret);
|
|
+ int (*sd_bus_set_address)(void* bus, const char* address);
|
|
+ int (*sd_bus_set_bus_client)(void* bus, int b);
|
|
+ int (*sd_bus_start)(void* bus);
|
|
+ int (*sd_bus_set_method_call_timeout)(void* bus, uint64_t timeout);
|
|
+ void* (*sd_bus_flush_close_unref)(void* bus);
|
|
+ int (*sd_bus_match_signal)(void* bus, void** ret_slot, const char* sender, const char* path,
|
|
+ const char* interface, const char* member, iokitd_bus_handler callback, void* userdata);
|
|
+ int (*sd_bus_call_method)(void* bus, const char* destination, const char* path,
|
|
+ const char* interface, const char* member, struct iokitd_bus_error* ret_error,
|
|
+ void** reply, const char* types, ...);
|
|
+ int (*sd_bus_get_property_trivial)(void* bus, const char* destination, const char* path,
|
|
+ const char* interface, const char* member, struct iokitd_bus_error* ret_error,
|
|
+ char type, void* ret_ptr);
|
|
+ int (*sd_bus_message_read)(void* m, const char* types, ...);
|
|
+ void* (*sd_bus_message_unref)(void* m);
|
|
+ void* (*sd_bus_slot_unref)(void* slot);
|
|
+ void (*sd_bus_error_free)(struct iokitd_bus_error* e);
|
|
+ int (*sd_bus_process)(void* bus, void** ret);
|
|
+ int (*sd_bus_wait)(void* bus, uint64_t timeout_usec);
|
|
+
|
|
+ /* Host file descriptors (the inhibitor lock) */
|
|
+ int (*fcntl)(int fd, int cmd, ...);
|
|
+ int (*close)(int fd);
|
|
+
|
|
+ /* Process environment (DBUS_SYSTEM_BUS_ADDRESS) */
|
|
+ char* (*getenv)(const char* name);
|
|
+
|
|
+ /* Optional diagnostics */
|
|
+ void (*log)(const char* message);
|
|
+
|
|
+ /*
|
|
+ * How long to wait for applications to acknowledge a suspend before
|
|
+ * releasing the inhibitor anyway; 0 derives it from logind's
|
|
+ * InhibitDelayMaxUSec (one second less, so the release always comes
|
|
+ * before logind gives up on us).
|
|
+ */
|
|
+ uint64_t ack_timeout_usec;
|
|
+};
|
|
+
|
|
+/* Shared host symbol table. Only implemented in the guest iokitd bridge. */
|
|
+const struct iokitd_logind_api* iokitd_host_power_api(void);
|
|
+
|
|
+struct iokitd_logind_callbacks
|
|
+{
|
|
+ /* Suspend is imminent. Call iokitd_logind_sleep_ready(cycle) once clients agreed. */
|
|
+ void (*will_sleep)(void* context, uint64_t cycle);
|
|
+ /* The system has resumed (or the suspend failed). */
|
|
+ void (*did_wake)(void* context, uint64_t cycle);
|
|
+};
|
|
+
|
|
+struct iokitd_logind;
|
|
+
|
|
+/*
|
|
+ * Connects to the system bus (DBUS_SYSTEM_BUS_ADDRESS or the standard
|
|
+ * socket), subscribes to PrepareForSleep, takes the delay inhibitor and
|
|
+ * starts the bus thread. A missing logind is not an error: no events arrive
|
|
+ * then. Returns 0 or a negative errno (no bus, incomplete api).
|
|
+ */
|
|
+int iokitd_logind_start(const struct iokitd_logind_api* api,
|
|
+ const struct iokitd_logind_callbacks* callbacks, void* context,
|
|
+ struct iokitd_logind** ret);
|
|
+
|
|
+/* Releases the inhibitor if it is still held for this suspend cycle. */
|
|
+void iokitd_logind_sleep_ready(struct iokitd_logind* logind, uint64_t cycle);
|
|
+
|
|
+/* Acknowledgement timeout in use, in microseconds. */
|
|
+uint64_t iokitd_logind_ack_timeout_usec(const struct iokitd_logind* logind);
|
|
+
|
|
+/* 1 if a delay inhibitor is currently held. */
|
|
+int iokitd_logind_holds_inhibitor(struct iokitd_logind* logind);
|
|
+
|
|
+/* Stops the bus thread, closes the inhibitor and the bus. */
|
|
+void iokitd_logind_stop(struct iokitd_logind* logind);
|
|
+
|
|
+#ifdef __cplusplus
|
|
+}
|
|
+#endif
|
|
+
|
|
+#endif
|
|
diff --git a/src/HostPowerInhibit.c b/src/HostPowerInhibit.c
|
|
new file mode 100644
|
|
index 0000000..cdcb933
|
|
--- /dev/null
|
|
+++ b/src/HostPowerInhibit.c
|
|
@@ -0,0 +1,118 @@
|
|
+/* Host power-save inhibitors. GPL-3.0-or-later, like iokitd. */
|
|
+#include "HostPowerInhibit.h"
|
|
+#include <stdio.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <unistd.h>
|
|
+
|
|
+struct iokitd_power_inhibit {
|
|
+ struct iokitd_logind_api api;
|
|
+ int idle_fd;
|
|
+ void* screen_bus;
|
|
+ uint32_t screen_cookie;
|
|
+ int screen_held;
|
|
+};
|
|
+
|
|
+static int connect_bus(const struct iokitd_logind_api* api, const char* address, void** bus)
|
|
+{
|
|
+ int r = api->sd_bus_new(bus);
|
|
+ if (r >= 0) r = api->sd_bus_set_address(*bus, address);
|
|
+ if (r >= 0) r = api->sd_bus_set_bus_client(*bus, 1);
|
|
+ /* Assertions are synchronous MIG calls; do not stall the IOKit queue for
|
|
+ * sd-bus's default 25-second service timeout. */
|
|
+ if (r >= 0) r = api->sd_bus_set_method_call_timeout(*bus, 500000);
|
|
+ if (r >= 0) r = api->sd_bus_start(*bus);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static int take_idle_lock(struct iokitd_power_inhibit* inhibitor)
|
|
+{
|
|
+ const struct iokitd_logind_api* api = &inhibitor->api;
|
|
+ const char* address = api->getenv("DBUS_SYSTEM_BUS_ADDRESS");
|
|
+ struct iokitd_bus_error error = {0};
|
|
+ void *bus = NULL, *reply = NULL;
|
|
+ int fd = -1;
|
|
+ if (!address || !address[0]) address = IOKITD_LOGIND_DEFAULT_SYSTEM_BUS;
|
|
+ int r = connect_bus(api, address, &bus);
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_call_method(bus, "org.freedesktop.login1", "/org/freedesktop/login1",
|
|
+ "org.freedesktop.login1.Manager", "Inhibit", &error, &reply, "ssss",
|
|
+ "idle", "Darling", "macOS application prevents automatic idle sleep", "block");
|
|
+ if (r >= 0) r = api->sd_bus_message_read(reply, "h", &fd);
|
|
+ if (r >= 0) {
|
|
+ inhibitor->idle_fd = api->fcntl(fd, IOKITD_LINUX_F_DUPFD_CLOEXEC, 3);
|
|
+ if (inhibitor->idle_fd < 0) r = -IOKITD_LINUX_ENOMEM;
|
|
+ }
|
|
+ if (reply) api->sd_bus_message_unref(reply);
|
|
+ api->sd_bus_error_free(&error);
|
|
+ if (bus) api->sd_bus_flush_close_unref(bus);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static int take_screen_lock(struct iokitd_power_inhibit* inhibitor)
|
|
+{
|
|
+ const struct iokitd_logind_api* api = &inhibitor->api;
|
|
+ const char* address = api->getenv("DBUS_SESSION_BUS_ADDRESS");
|
|
+ char fallback[512];
|
|
+ if (!address || !address[0]) {
|
|
+ const char* runtime = api->getenv("XDG_RUNTIME_DIR");
|
|
+ int length = runtime && runtime[0]
|
|
+ ? snprintf(fallback, sizeof(fallback), "unix:path=%s/bus", runtime)
|
|
+ : snprintf(fallback, sizeof(fallback), "unix:path=/run/user/%u/bus", (unsigned)getuid());
|
|
+ if (length < 0 || (size_t)length >= sizeof(fallback)) return -IOKITD_LINUX_EINVAL;
|
|
+ address = fallback;
|
|
+ }
|
|
+ struct iokitd_bus_error error = {0};
|
|
+ void* reply = NULL;
|
|
+ int r = connect_bus(api, address, &inhibitor->screen_bus);
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_call_method(inhibitor->screen_bus, "org.freedesktop.ScreenSaver",
|
|
+ "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "Inhibit",
|
|
+ &error, &reply, "ss", "Darling", "macOS application prevents display sleep");
|
|
+ if (r >= 0) r = api->sd_bus_message_read(reply, "u", &inhibitor->screen_cookie);
|
|
+ if (r >= 0) inhibitor->screen_held = 1;
|
|
+ if (reply) api->sd_bus_message_unref(reply);
|
|
+ api->sd_bus_error_free(&error);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+int iokitd_power_inhibit_create(const struct iokitd_logind_api* api, int display,
|
|
+ struct iokitd_power_inhibit** result)
|
|
+{
|
|
+ *result = NULL;
|
|
+ if (!api || !api->sd_bus_new || !api->sd_bus_set_address || !api->sd_bus_set_bus_client ||
|
|
+ !api->sd_bus_set_method_call_timeout || !api->sd_bus_start || !api->sd_bus_call_method ||
|
|
+ !api->sd_bus_message_read || !api->sd_bus_message_unref || !api->sd_bus_error_free ||
|
|
+ !api->sd_bus_flush_close_unref || !api->fcntl || !api->close || !api->getenv)
|
|
+ return -IOKITD_LINUX_ENOSYS;
|
|
+ struct iokitd_power_inhibit* inhibitor = calloc(1, sizeof(*inhibitor));
|
|
+ if (!inhibitor) return -IOKITD_LINUX_ENOMEM;
|
|
+ inhibitor->api = *api;
|
|
+ inhibitor->idle_fd = -1;
|
|
+ int r = take_idle_lock(inhibitor);
|
|
+ if (r >= 0 && display) r = take_screen_lock(inhibitor);
|
|
+ if (r < 0) {
|
|
+ iokitd_power_inhibit_destroy(inhibitor);
|
|
+ return r;
|
|
+ }
|
|
+ *result = inhibitor;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void iokitd_power_inhibit_destroy(struct iokitd_power_inhibit* inhibitor)
|
|
+{
|
|
+ if (!inhibitor) return;
|
|
+ const struct iokitd_logind_api* api = &inhibitor->api;
|
|
+ if (inhibitor->screen_held) {
|
|
+ struct iokitd_bus_error error = {0};
|
|
+ void* reply = NULL;
|
|
+ api->sd_bus_call_method(inhibitor->screen_bus, "org.freedesktop.ScreenSaver",
|
|
+ "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "UnInhibit",
|
|
+ &error, &reply, "u", inhibitor->screen_cookie);
|
|
+ if (reply) api->sd_bus_message_unref(reply);
|
|
+ api->sd_bus_error_free(&error);
|
|
+ }
|
|
+ if (inhibitor->screen_bus) api->sd_bus_flush_close_unref(inhibitor->screen_bus);
|
|
+ if (inhibitor->idle_fd >= 0) api->close(inhibitor->idle_fd);
|
|
+ free(inhibitor);
|
|
+}
|
|
diff --git a/src/HostPowerInhibit.h b/src/HostPowerInhibit.h
|
|
new file mode 100644
|
|
index 0000000..fed7fbe
|
|
--- /dev/null
|
|
+++ b/src/HostPowerInhibit.h
|
|
@@ -0,0 +1,18 @@
|
|
+/* Host power-save inhibitors. GPL-3.0-or-later, like iokitd. */
|
|
+#ifndef IOKITD_HOST_POWER_INHIBIT_H
|
|
+#define IOKITD_HOST_POWER_INHIBIT_H
|
|
+#include "LogindSleep.h"
|
|
+#ifdef __cplusplus
|
|
+extern "C" {
|
|
+#endif
|
|
+struct iokitd_power_inhibit;
|
|
+/* display=0 prevents automatic system idle sleep through logind. display=1
|
|
+ * additionally inhibits the desktop ScreenSaver; it fails if that service is
|
|
+ * unavailable, allowing the caller to try a real X11 display backend. */
|
|
+int iokitd_power_inhibit_create(const struct iokitd_logind_api* api, int display,
|
|
+ struct iokitd_power_inhibit** result);
|
|
+void iokitd_power_inhibit_destroy(struct iokitd_power_inhibit* inhibitor);
|
|
+#ifdef __cplusplus
|
|
+}
|
|
+#endif
|
|
+#endif
|
|
diff --git a/src/PowerAssertions.h b/src/PowerAssertions.h
|
|
index 1e4a975..3b0d416 100644
|
|
--- a/src/PowerAssertions.h
|
|
+++ b/src/PowerAssertions.h
|
|
@@ -28,11 +28,11 @@ void initPM();
|
|
class PowerAssertion
|
|
{
|
|
public:
|
|
- // How many AppAssertions instances reference this assertion (*not* a sum of references within all these AppAssertions instances)
|
|
- void addRef();
|
|
+ // One backend hold per enabled assertion; retaining an ID does not add holds.
|
|
+ IOReturn addRef();
|
|
void delRef();
|
|
protected:
|
|
- virtual void activate() = 0;
|
|
+ virtual IOReturn activate() = 0;
|
|
virtual void deactivate() = 0;
|
|
private:
|
|
int m_numHoldingApps = 0;
|
|
@@ -44,15 +44,17 @@ private:
|
|
AppAssertions(int pid);
|
|
~AppAssertions();
|
|
public:
|
|
- static AppAssertions* get(int pid);
|
|
+ static AppAssertions* get(int pid, bool create = true);
|
|
|
|
struct HeldAssertion
|
|
{
|
|
int refcount;
|
|
PowerAssertion* assertion;
|
|
+ bool enabled;
|
|
};
|
|
HeldAssertion* getAssertion(int assertionId);
|
|
- int createAssertion(PowerAssertion* which);
|
|
+ IOReturn createAssertion(PowerAssertion* which, bool enabled, int* assertionId);
|
|
+ IOReturn setEnabled(HeldAssertion* assertion, bool enabled);
|
|
void killAssertion(int assertionId);
|
|
private:
|
|
std::unordered_map<int, HeldAssertion> m_heldAssertions;
|
|
diff --git a/src/PowerAssertions.mm b/src/PowerAssertions.mm
|
|
index fa55d2f..810fe04 100644
|
|
--- a/src/PowerAssertions.mm
|
|
+++ b/src/PowerAssertions.mm
|
|
@@ -19,9 +19,11 @@
|
|
|
|
#include "PowerAssertions.h"
|
|
#include "PowerAssertionsX11.h"
|
|
+#include "HostPowerInhibit.h"
|
|
#include <stdexcept>
|
|
#include <iostream>
|
|
#include <string>
|
|
+#include <climits>
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <bsm/libbsm.h>
|
|
#import <Foundation/NSString.h>
|
|
@@ -30,34 +32,52 @@ extern "C" {
|
|
#include "powermanagementServer.h"
|
|
}
|
|
|
|
+class PowerAssertionPreventIdleSleep : public PowerAssertion
|
|
+{
|
|
+ struct iokitd_power_inhibit* m_host = nullptr;
|
|
+ IOReturn activate() override
|
|
+ {
|
|
+ const auto* api = iokitd_host_power_api();
|
|
+ return api && iokitd_power_inhibit_create(api, 0, &m_host) == 0
|
|
+ ? kIOReturnSuccess : kIOReturnNotReady;
|
|
+ }
|
|
+ void deactivate() override
|
|
+ {
|
|
+ iokitd_power_inhibit_destroy(m_host);
|
|
+ m_host = nullptr;
|
|
+ }
|
|
+};
|
|
+
|
|
std::unordered_map<int, AppAssertions*> AppAssertions::m_instances;
|
|
static std::unordered_map<std::string, PowerAssertion*> g_assertionKinds;
|
|
|
|
void initPM()
|
|
{
|
|
- NSString* str = (NSString*) kIOPMAssertPreventUserIdleDisplaySleep;
|
|
- g_assertionKinds.insert(std::make_pair([str UTF8String], new PowerAssertionPreventDisplaySleep));
|
|
+ // Legacy and modern spellings share one backend and one hold count.
|
|
+ static PowerAssertionPreventIdleSleep idle;
|
|
+ static PowerAssertionPreventDisplaySleep display;
|
|
+ g_assertionKinds = {
|
|
+ {[(NSString*)kIOPMAssertionTypeNoIdleSleep UTF8String], &idle},
|
|
+ {[(NSString*)kIOPMAssertPreventUserIdleSystemSleep UTF8String], &idle},
|
|
+ {[(NSString*)kIOPMAssertionTypeNoDisplaySleep UTF8String], &display},
|
|
+ {[(NSString*)kIOPMAssertPreventUserIdleDisplaySleep UTF8String], &display},
|
|
+ };
|
|
}
|
|
|
|
-static PowerAssertion* getByType(NSString* type)
|
|
-{
|
|
- auto it = g_assertionKinds.find([type UTF8String]);
|
|
- if (it != g_assertionKinds.end())
|
|
- return it->second;
|
|
- return nullptr;
|
|
-}
|
|
-
|
|
-void PowerAssertion::addRef()
|
|
+IOReturn PowerAssertion::addRef()
|
|
{
|
|
if (m_numHoldingApps == 0)
|
|
- activate();
|
|
+ {
|
|
+ IOReturn result = activate();
|
|
+ if (result != kIOReturnSuccess) return result;
|
|
+ }
|
|
m_numHoldingApps++;
|
|
+ return kIOReturnSuccess;
|
|
}
|
|
|
|
void PowerAssertion::delRef()
|
|
{
|
|
- m_numHoldingApps--;
|
|
- if (m_numHoldingApps == 0)
|
|
+ if (m_numHoldingApps > 0 && --m_numHoldingApps == 0)
|
|
deactivate();
|
|
}
|
|
|
|
@@ -66,37 +86,31 @@ AppAssertions::AppAssertions(int pid)
|
|
m_processSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
|
|
if (!m_processSource)
|
|
throw std::runtime_error("dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC) failed");
|
|
-
|
|
- if (m_processSource)
|
|
- {
|
|
- dispatch_source_set_event_handler(m_processSource, ^{
|
|
- m_instances.erase(pid);
|
|
- delete this;
|
|
- });
|
|
- dispatch_resume(m_processSource);
|
|
- }
|
|
+ dispatch_source_set_event_handler(m_processSource, ^{
|
|
+ m_instances.erase(pid);
|
|
+ delete this;
|
|
+ });
|
|
+ dispatch_resume(m_processSource);
|
|
}
|
|
|
|
AppAssertions::~AppAssertions()
|
|
{
|
|
- for (auto& [assertionId, ha] : m_heldAssertions)
|
|
- ha.assertion->delRef();
|
|
-
|
|
- if (m_processSource != nullptr)
|
|
- dispatch_release(m_processSource);
|
|
+ for (auto& item : m_heldAssertions)
|
|
+ if (item.second.enabled) item.second.assertion->delRef();
|
|
+ dispatch_source_cancel(m_processSource);
|
|
+ dispatch_release(m_processSource);
|
|
}
|
|
|
|
-AppAssertions* AppAssertions::get(int pid)
|
|
+AppAssertions* AppAssertions::get(int pid, bool create)
|
|
{
|
|
auto it = m_instances.find(pid);
|
|
- if (it != m_instances.end())
|
|
- return it->second;
|
|
-
|
|
+ if (it != m_instances.end()) return it->second;
|
|
+ if (!create) return nullptr;
|
|
try
|
|
{
|
|
- AppAssertions* aa = new AppAssertions(pid);
|
|
- m_instances.insert(std::make_pair(pid, aa));
|
|
- return aa;
|
|
+ AppAssertions* app = new AppAssertions(pid);
|
|
+ m_instances.emplace(pid, app);
|
|
+ return app;
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
@@ -108,126 +122,168 @@ AppAssertions* AppAssertions::get(int pid)
|
|
AppAssertions::HeldAssertion* AppAssertions::getAssertion(int assertionId)
|
|
{
|
|
auto it = m_heldAssertions.find(assertionId);
|
|
- if (it != m_heldAssertions.end())
|
|
- return &it->second;
|
|
- return nullptr;
|
|
+ return it == m_heldAssertions.end() ? nullptr : &it->second;
|
|
}
|
|
|
|
-int AppAssertions::createAssertion(PowerAssertion* which)
|
|
+IOReturn AppAssertions::createAssertion(PowerAssertion* which, bool enabled, int* assertionId)
|
|
{
|
|
+ if (m_nextAssertionNumber == INT_MAX) return kIOReturnNoResources;
|
|
+ if (enabled)
|
|
+ {
|
|
+ IOReturn result = which->addRef();
|
|
+ if (result != kIOReturnSuccess) return result;
|
|
+ }
|
|
int number = m_nextAssertionNumber++;
|
|
- HeldAssertion held = { 1, which };
|
|
- m_heldAssertions.insert(std::make_pair(number, held));
|
|
- which->addRef();
|
|
- return number;
|
|
+ try { m_heldAssertions.emplace(number, HeldAssertion{1, which, enabled}); }
|
|
+ catch (...)
|
|
+ {
|
|
+ if (enabled) which->delRef();
|
|
+ return kIOReturnNoMemory;
|
|
+ }
|
|
+ *assertionId = number;
|
|
+ return kIOReturnSuccess;
|
|
+}
|
|
+
|
|
+IOReturn AppAssertions::setEnabled(HeldAssertion* held, bool enabled)
|
|
+{
|
|
+ if (held->enabled == enabled) return kIOReturnSuccess;
|
|
+ if (enabled)
|
|
+ {
|
|
+ IOReturn result = held->assertion->addRef();
|
|
+ if (result != kIOReturnSuccess) return result;
|
|
+ }
|
|
+ else held->assertion->delRef();
|
|
+ held->enabled = enabled;
|
|
+ return kIOReturnSuccess;
|
|
}
|
|
|
|
void AppAssertions::killAssertion(int assertionId)
|
|
{
|
|
auto it = m_heldAssertions.find(assertionId);
|
|
- if (it == m_heldAssertions.end())
|
|
- return;
|
|
-
|
|
- it->second.assertion->delRef();
|
|
+ if (it == m_heldAssertions.end()) return;
|
|
+ if (it->second.enabled) it->second.assertion->delRef();
|
|
m_heldAssertions.erase(it);
|
|
}
|
|
|
|
-kern_return_t _io_pm_assertion_create
|
|
-(
|
|
- mach_port_t server,
|
|
- audit_token_t token,
|
|
- vm_offset_t props,
|
|
- mach_msg_type_number_t propsCnt,
|
|
- int *assertion_id,
|
|
- int *disableAppSleep,
|
|
- int *enTrIntensity,
|
|
- int *return_code
|
|
-)
|
|
+// These MIG routines return their IOReturn in return_code and KERN_SUCCESS for
|
|
+// transport. Consequently they own (and consume) the request's OOL data even
|
|
+// when validation fails; dispatch_mig_server must not destroy it a second time.
|
|
+static CFDictionaryRef copyProperties(vm_offset_t props, mach_msg_type_number_t count)
|
|
{
|
|
- if (!props)
|
|
- return KERN_INVALID_ARGUMENT;
|
|
-
|
|
- CFDataRef data = CFDataCreateWithBytesNoCopy(nullptr, (const UInt8*) props, propsCnt, kCFAllocatorNull);
|
|
- CFDictionaryRef properties = (CFDictionaryRef) CFPropertyListCreateWithData(nullptr, data, kCFPropertyListImmutable, nullptr, nullptr);
|
|
-
|
|
- CFRelease(data);
|
|
-
|
|
- vm_deallocate(mach_task_self(), props, propsCnt);
|
|
-
|
|
- if (!properties)
|
|
- return KERN_INVALID_ARGUMENT;
|
|
-
|
|
- if (CFGetTypeID(properties) != CFDictionaryGetTypeID())
|
|
+ CFDictionaryRef result = nullptr;
|
|
+ if (props && count)
|
|
{
|
|
- std::cerr << "Unexpected properties data type\n";
|
|
- CFRelease(properties);
|
|
- return KERN_INVALID_ARGUMENT;
|
|
+ CFDataRef data = CFDataCreateWithBytesNoCopy(nullptr, (const UInt8*)props, count, kCFAllocatorNull);
|
|
+ if (data)
|
|
+ {
|
|
+ result = (CFDictionaryRef)CFPropertyListCreateWithData(nullptr, data, kCFPropertyListImmutable, nullptr, nullptr);
|
|
+ CFRelease(data);
|
|
+ }
|
|
}
|
|
-
|
|
- CFStringRef cftype = (CFStringRef) CFDictionaryGetValue(properties, kIOPMAssertionTypeKey);
|
|
-
|
|
- if (!cftype || CFStringGetTypeID() != CFGetTypeID(cftype))
|
|
+ if (props) vm_deallocate(mach_task_self(), props, count);
|
|
+ if (result && CFGetTypeID(result) != CFDictionaryGetTypeID())
|
|
{
|
|
- std::cerr << "kIOPMAssertionTypeKey is not a string\n";
|
|
- CFRelease(properties);
|
|
- return KERN_INVALID_ARGUMENT;
|
|
+ CFRelease(result);
|
|
+ result = nullptr;
|
|
}
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static bool assertionLevel(CFDictionaryRef properties, bool* enabled)
|
|
+{
|
|
+ CFTypeRef number = CFDictionaryGetValue(properties, kIOPMAssertionLevelKey);
|
|
+ if (!number) return true;
|
|
+ int level;
|
|
+ if (CFGetTypeID(number) != CFNumberGetTypeID() ||
|
|
+ !CFNumberGetValue((CFNumberRef)number, kCFNumberIntType, &level) ||
|
|
+ (level != kIOPMAssertionLevelOff && level != kIOPMAssertionLevelOn)) return false;
|
|
+ *enabled = level == kIOPMAssertionLevelOn;
|
|
+ return true;
|
|
+}
|
|
|
|
- PowerAssertion* pa = getByType((NSString*) cftype);
|
|
- if (pa == nullptr)
|
|
+kern_return_t _io_pm_assertion_create(mach_port_t server, audit_token_t token,
|
|
+ vm_offset_t props, mach_msg_type_number_t propsCnt, int* assertion_id,
|
|
+ int* disableAppSleep, int* enTrIntensity, int* return_code)
|
|
+{
|
|
+ *assertion_id = kIOPMNullAssertionID;
|
|
+ *disableAppSleep = 0;
|
|
+ *enTrIntensity = -1;
|
|
+ *return_code = kIOReturnBadArgument;
|
|
+ CFDictionaryRef properties = copyProperties(props, propsCnt);
|
|
+ if (!properties) return KERN_SUCCESS;
|
|
+ CFTypeRef type = CFDictionaryGetValue(properties, kIOPMAssertionTypeKey);
|
|
+ bool enabled = true;
|
|
+ CFTypeRef timeout = CFDictionaryGetValue(properties, kIOPMAssertionTimeoutKey);
|
|
+ double seconds = 0;
|
|
+ if (timeout && (CFGetTypeID(timeout) != CFNumberGetTypeID() ||
|
|
+ !CFNumberGetValue((CFNumberRef)timeout, kCFNumberDoubleType, &seconds)))
|
|
+ *return_code = kIOReturnBadArgument;
|
|
+ else if (seconds != 0)
|
|
+ // Timed assertions need expiration scheduling. Do not silently turn
|
|
+ // a timed request into an indefinite host inhibition.
|
|
+ *return_code = kIOReturnUnsupported;
|
|
+ else if (type && CFGetTypeID(type) == CFStringGetTypeID() && assertionLevel(properties, &enabled))
|
|
{
|
|
- std::cerr << "Unsupported assertion type\n";
|
|
- CFRelease(properties);
|
|
- return KERN_INVALID_ARGUMENT;
|
|
+ auto it = g_assertionKinds.find([(NSString*)type UTF8String]);
|
|
+ if (it == g_assertionKinds.end()) *return_code = kIOReturnUnsupported;
|
|
+ else
|
|
+ {
|
|
+ pid_t callerPid;
|
|
+ audit_token_to_au32(token, nullptr, nullptr, nullptr, nullptr, nullptr, &callerPid, nullptr, nullptr);
|
|
+ AppAssertions* app = AppAssertions::get(callerPid);
|
|
+ *return_code = app ? app->createAssertion(it->second, enabled, assertion_id) : kIOReturnNoResources;
|
|
+ }
|
|
}
|
|
+ CFRelease(properties);
|
|
+ return KERN_SUCCESS;
|
|
+}
|
|
|
|
- pid_t callerPid;
|
|
- audit_token_to_au32(token, nullptr, nullptr, nullptr, nullptr, nullptr, &callerPid, nullptr, nullptr);
|
|
-
|
|
- // std::cout << "Creating assertion for PID " << callerPid << std::endl;
|
|
- *assertion_id = AppAssertions::get(callerPid)->createAssertion(pa);
|
|
- *return_code = 0;
|
|
-
|
|
- return KERN_SUCCESS;
|
|
-}
|
|
-
|
|
-kern_return_t _io_pm_assertion_retain_release
|
|
-(
|
|
- mach_port_t server,
|
|
- audit_token_t token,
|
|
- int assertion_id,
|
|
- int action,
|
|
- int *retainCnt,
|
|
- int *disableAppSleep,
|
|
- int *enableAppSleep,
|
|
- int *return_code
|
|
-)
|
|
+kern_return_t _io_pm_assertion_retain_release(mach_port_t server, audit_token_t token,
|
|
+ int assertion_id, int action, int* retainCnt, int* disableAppSleep,
|
|
+ int* enableAppSleep, int* return_code)
|
|
{
|
|
+ *retainCnt = *disableAppSleep = *enableAppSleep = 0;
|
|
+ *return_code = kIOReturnNotFound;
|
|
pid_t callerPid;
|
|
audit_token_to_au32(token, nullptr, nullptr, nullptr, nullptr, nullptr, &callerPid, nullptr, nullptr);
|
|
-
|
|
- AppAssertions* appAssertions = AppAssertions::get(callerPid);
|
|
- AppAssertions::HeldAssertion* ha = appAssertions->getAssertion(assertion_id);
|
|
-
|
|
- if (!ha)
|
|
- {
|
|
- std::cerr << "_io_pm_assertion_retain_release() cannot find assertion " << assertion_id << std::endl;
|
|
- return KERN_INVALID_ARGUMENT;
|
|
- }
|
|
-
|
|
- *return_code = 0;
|
|
+ AppAssertions* app = AppAssertions::get(callerPid, false);
|
|
+ auto* held = app ? app->getAssertion(assertion_id) : nullptr;
|
|
+ if (!held) return KERN_SUCCESS;
|
|
if (action == kIOPMAssertionMIGDoRetain)
|
|
{
|
|
- *retainCnt = ++ha->refcount;
|
|
+ if (held->refcount == INT_MAX) { *return_code = kIOReturnNoResources; return KERN_SUCCESS; }
|
|
+ *retainCnt = ++held->refcount;
|
|
}
|
|
else if (action == kIOPMAssertionMIGDoRelease)
|
|
{
|
|
- *retainCnt = --ha->refcount;
|
|
- if (ha->refcount == 0)
|
|
- appAssertions->killAssertion(assertion_id);
|
|
+ *retainCnt = --held->refcount;
|
|
+ if (!held->refcount) app->killAssertion(assertion_id);
|
|
}
|
|
- else
|
|
- return KERN_INVALID_ARGUMENT;
|
|
+ else { *return_code = kIOReturnBadArgument; return KERN_SUCCESS; }
|
|
+ *return_code = kIOReturnSuccess;
|
|
+ return KERN_SUCCESS;
|
|
+}
|
|
|
|
- return KERN_NOT_SUPPORTED;
|
|
+kern_return_t _io_pm_assertion_set_properties(mach_port_t server, audit_token_t token,
|
|
+ int assertion_id, vm_offset_t props, mach_msg_type_number_t propsCnt,
|
|
+ int* disableAppSleep, int* enableAppSleep, int* enTrIntensity, int* return_code)
|
|
+{
|
|
+ *disableAppSleep = *enableAppSleep = 0;
|
|
+ *enTrIntensity = -1;
|
|
+ *return_code = kIOReturnBadArgument;
|
|
+ CFDictionaryRef properties = copyProperties(props, propsCnt);
|
|
+ if (!properties) return KERN_SUCCESS;
|
|
+ bool enabled = true;
|
|
+ if (CFDictionaryGetCount(properties) != 1 || !CFDictionaryContainsKey(properties, kIOPMAssertionLevelKey))
|
|
+ *return_code = kIOReturnUnsupported;
|
|
+ else if (assertionLevel(properties, &enabled))
|
|
+ {
|
|
+ pid_t callerPid;
|
|
+ audit_token_to_au32(token, nullptr, nullptr, nullptr, nullptr, nullptr, &callerPid, nullptr, nullptr);
|
|
+ AppAssertions* app = AppAssertions::get(callerPid, false);
|
|
+ auto* held = app ? app->getAssertion(assertion_id) : nullptr;
|
|
+ *return_code = held ? app->setEnabled(held, enabled) : kIOReturnNotFound;
|
|
+ }
|
|
+ CFRelease(properties);
|
|
+ return KERN_SUCCESS;
|
|
}
|
|
diff --git a/src/PowerAssertionsX11.h b/src/PowerAssertionsX11.h
|
|
index 2905edd..3b4ea10 100644
|
|
--- a/src/PowerAssertionsX11.h
|
|
+++ b/src/PowerAssertionsX11.h
|
|
@@ -20,12 +20,16 @@
|
|
#ifndef POWERASSERTIONSX11_H
|
|
#define POWERASSERTIONSX11_H
|
|
#include "PowerAssertions.h"
|
|
+#include "HostPowerInhibit.h"
|
|
|
|
class PowerAssertionPreventDisplaySleep : public PowerAssertion
|
|
{
|
|
protected:
|
|
- void activate() override;
|
|
+ IOReturn activate() override;
|
|
void deactivate() override;
|
|
+private:
|
|
+ struct iokitd_power_inhibit* m_host = nullptr;
|
|
+ bool m_changedDPMS = false;
|
|
};
|
|
|
|
#endif
|
|
diff --git a/src/PowerAssertionsX11.mm b/src/PowerAssertionsX11.mm
|
|
index 7a6fcfa..26a7d68 100644
|
|
--- a/src/PowerAssertionsX11.mm
|
|
+++ b/src/PowerAssertionsX11.mm
|
|
@@ -26,28 +26,37 @@
|
|
#include <X11/extensions/dpms.h>
|
|
#undef BOOL
|
|
|
|
-void PowerAssertionPreventDisplaySleep::activate()
|
|
+IOReturn PowerAssertionPreventDisplaySleep::activate()
|
|
{
|
|
- Display* display = IODisplayConnectX11::getDisplay();
|
|
- if (!display)
|
|
- return;
|
|
+ const auto* api = iokitd_host_power_api();
|
|
+ if (api && iokitd_power_inhibit_create(api, 1, &m_host) == 0)
|
|
+ return kIOReturnSuccess;
|
|
|
|
+ // Desktop inhibition is preferred on Wayland; retain the real X11
|
|
+ // fallback when no ScreenSaver service is available.
|
|
+ Display* display = IODisplayConnectX11::getDisplay();
|
|
int eventBase, errorBase;
|
|
- if (DPMSQueryExtension(display, &eventBase, &errorBase))
|
|
- {
|
|
- DPMSDisable(display);
|
|
- }
|
|
+ CARD16 power;
|
|
+ unsigned char enabled;
|
|
+ if (!display || !DPMSQueryExtension(display, &eventBase, &errorBase) ||
|
|
+ !DPMSCapable(display) || !DPMSInfo(display, &power, &enabled))
|
|
+ return kIOReturnNotReady;
|
|
+ if (api)
|
|
+ iokitd_power_inhibit_create(api, 0, &m_host);
|
|
+ m_changedDPMS = enabled;
|
|
+ if (m_changedDPMS) DPMSDisable(display);
|
|
+ XFlush(display);
|
|
+ return kIOReturnSuccess;
|
|
}
|
|
|
|
void PowerAssertionPreventDisplaySleep::deactivate()
|
|
{
|
|
- Display* display = IODisplayConnectX11::getDisplay();
|
|
- if (!display)
|
|
- return;
|
|
-
|
|
- int eventBase, errorBase;
|
|
- if (DPMSQueryExtension(display, &eventBase, &errorBase))
|
|
+ iokitd_power_inhibit_destroy(m_host);
|
|
+ m_host = nullptr;
|
|
+ if (m_changedDPMS)
|
|
{
|
|
- DPMSEnable(display);
|
|
+ Display* display = IODisplayConnectX11::getDisplay();
|
|
+ if (display) { DPMSEnable(display); XFlush(display); }
|
|
+ m_changedDPMS = false;
|
|
}
|
|
}
|
|
diff --git a/src/IORegistryEntry.h b/src/IORegistryEntry.h
|
|
index 90155ed..e955bd4 100644
|
|
--- a/src/IORegistryEntry.h
|
|
+++ b/src/IORegistryEntry.h
|
|
@@ -19,6 +19,8 @@ public:
|
|
|
|
std::string_view getPathName(const char* plane);
|
|
std::string getPath(const char* plane);
|
|
+ bool inPlane(const char* plane) const;
|
|
+ static IORegistryEntry* fromPath(std::string_view path);
|
|
std::set<IORegistryEntry*> getParents(const char* plane);
|
|
std::set<IORegistryEntry*> getChildren(const char* plane);
|
|
std::string getName(const char* plane = nullptr);
|
|
@@ -32,8 +34,8 @@ public:
|
|
void registerInPlane(const char* plane, const char* pathName, IORegistryEntry* parent);
|
|
protected:
|
|
// The key is the plane name, e.g. kIOServicePlane (as declared in IOKit/IOKitKeys.h)
|
|
- std::unordered_map<std::string_view, std::string> m_name;
|
|
- std::unordered_map<std::string_view, std::set<IORegistryEntry*>> m_parents, m_children;
|
|
+ std::unordered_map<std::string, std::string> m_name;
|
|
+ std::unordered_map<std::string, std::set<IORegistryEntry*>> m_parents, m_children;
|
|
uint64_t m_id;
|
|
|
|
static std::unordered_map<uint64_t, IORegistryEntry*> m_entriesById;
|
|
diff --git a/src/PlatformIdentity.h b/src/PlatformIdentity.h
|
|
new file mode 100644
|
|
index 0000000..1f83b65
|
|
--- /dev/null
|
|
+++ b/src/PlatformIdentity.h
|
|
@@ -0,0 +1,78 @@
|
|
+#ifndef DARLING_PLATFORM_IDENTITY_H
|
|
+#define DARLING_PLATFORM_IDENTITY_H
|
|
+#import <Foundation/Foundation.h>
|
|
+#include <uuid/uuid.h>
|
|
+#include <cstdio>
|
|
+#include <cstring>
|
|
+#include <array>
|
|
+
|
|
+namespace DarlingPlatformIdentity {
|
|
+inline bool printable(id value, size_t maximum) {
|
|
+ if (![value isKindOfClass:[NSString class]]) return false;
|
|
+ const char* text = [value UTF8String];
|
|
+ if (!text) return false;
|
|
+ size_t length = strlen(text);
|
|
+ if (!length || length > maximum || length != [value length]) return false;
|
|
+ for (size_t i = 0; i < length; ++i) if ((unsigned char)text[i] < 32 || (unsigned char)text[i] > 126) return false;
|
|
+ return true;
|
|
+}
|
|
+
|
|
+// Validate the entire schema before exposing any overrides. Strings on device
|
|
+// tree properties are NUL-terminated NSData; serial/UUID retain NSString types.
|
|
+inline NSDictionary* properties(id configuration) {
|
|
+ if (![configuration isKindOfClass:[NSDictionary class]] || [configuration count] != 2) return nil;
|
|
+ id schema = [configuration objectForKey:@"schema"];
|
|
+ if (![schema isKindOfClass:[NSNumber class]] || CFGetTypeID((CFTypeRef)schema) == CFBooleanGetTypeID() ||
|
|
+ strchr("fd", [schema objCType][0]) || [schema longLongValue] != 1) return nil;
|
|
+ id source = [configuration objectForKey:@"properties"];
|
|
+ if (![source isKindOfClass:[NSDictionary class]] || [source count] > 9) return nil;
|
|
+ NSMutableDictionary* result = [NSMutableDictionary dictionary];
|
|
+ for (id key in source) {
|
|
+ if (![key isKindOfClass:[NSString class]]) return nil;
|
|
+ id value = [source objectForKey:key];
|
|
+ if ([key isEqual:@"IOPlatformUUID"]) {
|
|
+ if (!printable(value, 36) || [value length] != 36) return nil;
|
|
+ uuid_t uuid;
|
|
+ if (uuid_parse([value UTF8String], uuid) || uuid_is_null(uuid)) return nil;
|
|
+ uuid_string_t text; uuid_unparse_upper(uuid, text);
|
|
+ result[key] = [NSString stringWithUTF8String:text];
|
|
+ unsigned char bytes[16]; memcpy(bytes, uuid, sizeof(bytes));
|
|
+ for (int i = 0; i < 4; ++i) bytes[i] = uuid[3-i];
|
|
+ bytes[4] = uuid[5]; bytes[5] = uuid[4]; bytes[6] = uuid[7]; bytes[7] = uuid[6];
|
|
+ NSData* systemID = [NSData dataWithBytes:bytes length:16];
|
|
+ if (source[@"system-id"] && ![source[@"system-id"] isEqual:systemID]) return nil;
|
|
+ result[@"system-id"] = systemID;
|
|
+ } else if ([key isEqual:@"ROM"] || [key isEqual:@"system-id"]) {
|
|
+ if (![value isKindOfClass:[NSData class]] || [value length] != ([key isEqual:@"ROM"] ? 6 : 16)) return nil;
|
|
+ result[key] = value;
|
|
+ } else if ([key isEqual:@"IOPlatformSerialNumber"]) {
|
|
+ if (!printable(value, 64)) return nil;
|
|
+ result[key] = value;
|
|
+ } else if ([key isEqual:@"manufacturer"] || [key isEqual:@"model"] || [key isEqual:@"product-name"] ||
|
|
+ [key isEqual:@"board-id"] || [key isEqual:@"mlb-serial-number"]) {
|
|
+ if (!printable(value, [key isEqual:@"mlb-serial-number"] ? 64 : 128)) return nil;
|
|
+ const char* text = [value UTF8String];
|
|
+ result[key] = [NSData dataWithBytes:text length:strlen(text)+1];
|
|
+ } else return nil;
|
|
+ }
|
|
+ if (result[@"model"] && !result[@"product-name"]) result[@"product-name"] = result[@"model"];
|
|
+ return result;
|
|
+}
|
|
+
|
|
+inline NSDictionary* load(const char* path) {
|
|
+ FILE* file = fopen(path, "rb");
|
|
+ if (!file) return nil; // no overrides is the normal, unchanged default
|
|
+ std::array<unsigned char, 65537> buffer;
|
|
+ size_t size = fread(buffer.data(), 1, buffer.size(), file);
|
|
+ bool failed = ferror(file); fclose(file);
|
|
+ if (!failed && size <= 65536) {
|
|
+ NSData* data = [NSData dataWithBytes:buffer.data() length:size];
|
|
+ id config = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:NULL];
|
|
+ NSDictionary* overrides = properties(config);
|
|
+ if (overrides) return overrides;
|
|
+ }
|
|
+ fprintf(stderr, "iokitd: invalid darling-platform.plist; retaining default platform properties\n");
|
|
+ return nil;
|
|
+}
|
|
+}
|
|
+#endif
|