mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Implement legacy IOKit power assertions with host idle inhibitors
This commit is contained in:
1 parent
e2f1392369
commit
e554644d0d
8 files changed
+1149
-23
No files matched your search
+43
-1
@@ -82,6 +82,7 @@ cc -Wall -Wextra -Werror -pthread \
|
||||
-I build/darling-src/src/external/iokitd/src \
|
||||
tools/riot-logind-sleep-check.c \
|
||||
build/darling-src/src/external/iokitd/src/LogindSleep.c \
|
||||
build/darling-src/src/external/iokitd/src/HostPowerInhibit.c \
|
||||
$(pkg-config --cflags --libs libsystemd) -o build/riot-logind-sleep-check
|
||||
build/riot-logind-sleep-check
|
||||
```
|
||||
@@ -90,7 +91,7 @@ The host check uses a private D-Bus daemon and fake logind. It covers absent
|
||||
services, delay-lock ownership, acknowledgement deadlines, stale
|
||||
acknowledgements, repeated sleep/wake, a logind restart, a full system-bus
|
||||
restart and a sleep signal during startup timeout discovery. Expected:
|
||||
`LOGIND_SLEEP PASS checks=57 failures=0`. It also runs with
|
||||
`LOGIND_SLEEP PASS checks=73 failures=0`. It also runs with
|
||||
`-fsanitize=address,undefined`.
|
||||
|
||||
`IOServiceOpen` sends an empty out-of-line descriptor. Older Darling servers
|
||||
@@ -117,6 +118,47 @@ requires a new lock from the persistent launcher plus confirmation that its
|
||||
shell has started. Lock numbering is relative to that launcher, rather than
|
||||
assuming that the first recorded lock belongs to the test clients.
|
||||
|
||||
## Power-save assertions
|
||||
|
||||
Chromium uses the legacy names `NoIdleSleepAssertion` and
|
||||
`NoDisplaySleepAssertion` with `IOPMAssertionCreateWithName`. These share
|
||||
backends with the modern `PreventUserIdleSystemSleep` and
|
||||
`PreventUserIdleDisplaySleep` names. System-idle prevention uses a logind
|
||||
`idle/block` inhibitor; display prevention additionally holds a desktop
|
||||
`org.freedesktop.ScreenSaver.Inhibit` cookie. The existing X11 DPMS backend is
|
||||
a fallback when desktop inhibition is unavailable, and restores the prior
|
||||
DPMS enabled state. These assertions do not take a logind `sleep/block` lock.
|
||||
|
||||
Create, retain, release, level On/Off and process-exit cleanup are supported.
|
||||
The last enabled assertion releases the backend; retaining an assertion ID
|
||||
does not add another backend hold. Unsupported types and nonzero assertion
|
||||
timeouts return `kIOReturnUnsupported`, rather than silently creating an
|
||||
indefinite inhibitor. Host-backend failures are returned to the caller.
|
||||
|
||||
After the builds above:
|
||||
|
||||
```sh
|
||||
python3 tools/build-riot-power-assertion-check.py
|
||||
python3 tools/riot-power-assertion-integration-check.py
|
||||
```
|
||||
|
||||
Expected: `POWER_ASSERTION_INTEGRATION PASS`. The fixture uses private fake
|
||||
logind and ScreenSaver services for both D-Bus addresses and a separate Darling
|
||||
prefix. It checks legacy/modern aliases, multiple clients, retain/final release,
|
||||
level changes, failed requests and cleanup of assertions left held by an exiting
|
||||
process. It never contacts the real desktop's inhibitor services or launches
|
||||
Riot. The native check above additionally verifies backend-denial and missing-bus
|
||||
cleanup, and passes under AddressSanitizer/UndefinedBehaviorSanitizer.
|
||||
|
||||
The assertion integration fails against the previously installed `iokitd` with
|
||||
the reported `0xe00002c9` create error, and passes with the rebuilt service.
|
||||
Only `iokitd` needs reinstalling for this follow-up, with Darling stopped:
|
||||
|
||||
```sh
|
||||
sudo install -m755 build/darling-src/build/src/external/iokitd/iokitd \
|
||||
/usr/local/libexec/darling/usr/sbin/iokitd
|
||||
```
|
||||
|
||||
## Host URL hand-off regression
|
||||
|
||||
LaunchServices' D-Bus hand-off code (`LSHostOpenBus.c`) builds natively against the host's libsystemd. The check starts a private `dbus-daemon` with no service directories and a fake portal/systemd service, so nothing real is activated and no browser opens:
|
||||
|
||||
+712
-11
@@ -1,5 +1,5 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 84161fd..3f7fd2b 100644
|
||||
index 84161fd..5b23944 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -17,6 +17,8 @@ include_directories(
|
||||
@@ -11,7 +11,7 @@ index 84161fd..3f7fd2b 100644
|
||||
|
||||
mig(iokitmig.defs)
|
||||
mig(powermanagement.defs)
|
||||
@@ -36,7 +38,12 @@ set(iokitd_sources
|
||||
@@ -36,7 +38,13 @@ set(iokitd_sources
|
||||
src/IODisplayConnect.mm
|
||||
src/IODisplayConnectX11.mm
|
||||
src/PowerAssertions.mm
|
||||
@@ -21,6 +21,7 @@ index 84161fd..3f7fd2b 100644
|
||||
+ src/IOPMrootDomain.mm
|
||||
+ src/IOInterestNotification.mm
|
||||
+ src/LogindSleep.c
|
||||
+ src/HostPowerInhibit.c
|
||||
src/IOSurfaceRoot.mm
|
||||
src/IOSurface.mm
|
||||
src/IOUserClient.mm
|
||||
@@ -449,10 +450,36 @@ index 37ce9b8..e23aea0 100644
|
||||
+ IOPMrootDomain::registerSelf(registry);
|
||||
}
|
||||
diff --git a/src/stubs-powermanagement.c b/src/stubs-powermanagement.c
|
||||
index a568ecc..929746c 100644
|
||||
index a568ecc..c2d4063 100644
|
||||
--- a/src/stubs-powermanagement.c
|
||||
+++ b/src/stubs-powermanagement.c
|
||||
@@ -368,22 +368,6 @@ kern_return_t _io_ps_release_pspowersource
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1261,10 +1288,10 @@ index 0000000..9f9fc95
|
||||
+#endif
|
||||
diff --git a/src/IOPMrootDomain.mm b/src/IOPMrootDomain.mm
|
||||
new file mode 100644
|
||||
index 0000000..268e2f5
|
||||
index 0000000..9b6d30b
|
||||
--- /dev/null
|
||||
+++ b/src/IOPMrootDomain.mm
|
||||
@@ -0,0 +1,333 @@
|
||||
@@ -0,0 +1,342 @@
|
||||
+/*
|
||||
+ This file is part of Darling.
|
||||
+
|
||||
@@ -1516,6 +1543,15 @@ index 0000000..268e2f5
|
||||
+ 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(), ^{
|
||||
@@ -1534,17 +1570,17 @@ index 0000000..268e2f5
|
||||
+{
|
||||
+ // Connecting and taking the inhibitor are blocking D-Bus calls.
|
||||
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
+ static struct iokitd_logind_api api;
|
||||
+ 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 (!loadLogindApi(&api))
|
||||
+ 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);
|
||||
+ 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);
|
||||
@@ -2224,10 +2260,10 @@ index 0000000..1d6aa79
|
||||
+}
|
||||
diff --git a/src/LogindSleep.h b/src/LogindSleep.h
|
||||
new file mode 100644
|
||||
index 0000000..49dac3c
|
||||
index 0000000..a161a68
|
||||
--- /dev/null
|
||||
+++ b/src/LogindSleep.h
|
||||
@@ -0,0 +1,145 @@
|
||||
@@ -0,0 +1,148 @@
|
||||
+/*
|
||||
+ This file is part of Darling.
|
||||
+
|
||||
@@ -2336,6 +2372,9 @@ index 0000000..49dac3c
|
||||
+ 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. */
|
||||
@@ -2373,3 +2412,665 @@ index 0000000..49dac3c
|
||||
+#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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the power-save assertion guest probe (IOPMAssertionCreateWithName)."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
config = root / "build/config.json"
|
||||
sdk = json.loads(config.read_text())["sdk"] if config.exists() else "/usr/local/libexec/darling"
|
||||
appkit = root / "build/darling-src/build/src/external/cocotron/AppKit"
|
||||
flags = {}
|
||||
for line in (appkit / "CMakeFiles/AppKit.dir/flags.make").read_text().splitlines():
|
||||
if line.startswith("C_") and " = " in line:
|
||||
key, value = line.split(" = ", 1)
|
||||
flags[key] = shlex.split(value)
|
||||
|
||||
source = root / "tools/riot-power-assertion-check.c"
|
||||
obj = root / "build/riot-power-assertion-check.o"
|
||||
exe = root / "build/riot-power-assertion-check"
|
||||
subprocess.run(["clang", "-x", "objective-c", *flags["C_DEFINES"], *flags["C_INCLUDES"],
|
||||
*flags["C_FLAGS"], "-fblocks", "-c", str(source),
|
||||
"-o", str(obj)], check=True)
|
||||
subprocess.run(["clang", "-target", "x86_64-apple-macosx11.0",
|
||||
"-fuse-ld=lld", "-nostdlib", "-isysroot",
|
||||
sdk, str(obj), "-lSystem",
|
||||
"-framework", "IOKit", "-framework", "CoreFoundation",
|
||||
"-o", str(exe)],
|
||||
check=True)
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "LogindSleep.h"
|
||||
#include "HostPowerInhibit.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -61,6 +62,8 @@ static FILE *record_file;
|
||||
static int lock_fds[MAX_LOCKS], lock_ids[MAX_LOCKS], lock_count, next_lock_id = 1;
|
||||
static uint64_t sleep_started_ms;
|
||||
static int fake_sleeping;
|
||||
static int next_screen_cookie = 1, screen_denied;
|
||||
static int screen_cookies[MAX_LOCKS];
|
||||
|
||||
static void record(const char *format, ...) __attribute__((format(printf, 1, 2)));
|
||||
static void record(const char *format, ...) {
|
||||
@@ -89,6 +92,27 @@ static int logind_handler(sd_bus_message *m, void *userdata, sd_bus_error *error
|
||||
close(p[1]); /* the reply carried its own copy */
|
||||
return r;
|
||||
}
|
||||
if (sd_bus_message_is_method_call(m, "org.freedesktop.ScreenSaver", "Inhibit")) {
|
||||
const char *app, *reason;
|
||||
if (screen_denied) {
|
||||
record("ScreenDenied");
|
||||
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_ACCESS_DENIED, "inhibition denied");
|
||||
}
|
||||
if (sd_bus_message_read(m, "ss", &app, &reason) < 0 || next_screen_cookie >= MAX_LOCKS)
|
||||
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "bad screen inhibit");
|
||||
int cookie = next_screen_cookie++;
|
||||
screen_cookies[cookie] = 1;
|
||||
record("ScreenInhibit\t%d\t%s\t%s", cookie, app, reason);
|
||||
return sd_bus_reply_method_return(m, "u", (uint32_t)cookie);
|
||||
}
|
||||
if (sd_bus_message_is_method_call(m, "org.freedesktop.ScreenSaver", "UnInhibit")) {
|
||||
uint32_t cookie;
|
||||
if (sd_bus_message_read(m, "u", &cookie) < 0 || cookie >= MAX_LOCKS || !screen_cookies[cookie])
|
||||
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "unknown screen cookie");
|
||||
screen_cookies[cookie] = 0;
|
||||
record("ScreenRelease\t%u", cookie);
|
||||
return sd_bus_reply_method_return(m, "");
|
||||
}
|
||||
if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get")) {
|
||||
const char *iface, *name;
|
||||
if (sd_bus_message_read(m, "ss", &iface, &name) < 0)
|
||||
@@ -121,6 +145,9 @@ static void run_fake(int control_fd, int ready_fd) {
|
||||
sd_bus_set_bus_client(bus, 1) < 0 || sd_bus_start(bus) < 0)
|
||||
_exit(3);
|
||||
sd_bus_add_object(bus, NULL, "/org/freedesktop/login1", logind_handler, NULL);
|
||||
sd_bus_add_object(bus, NULL, "/org/freedesktop/ScreenSaver", logind_handler, NULL);
|
||||
if (sd_bus_request_name(bus, "org.freedesktop.ScreenSaver", 0) < 0)
|
||||
_exit(4);
|
||||
if (sd_bus_request_name(bus, "org.freedesktop.login1", 0) < 0)
|
||||
_exit(4);
|
||||
if (write(ready_fd, "R", 1) != 1)
|
||||
@@ -148,6 +175,7 @@ static void run_fake(int control_fd, int ready_fd) {
|
||||
break;
|
||||
if (c == 'S' || c == 'W')
|
||||
emit_prepare(bus, c == 'S');
|
||||
if (c == 'D' || c == 'A') { screen_denied = c == 'D'; record("ScreenDeny\t%d", screen_denied); }
|
||||
}
|
||||
for (int i = 0; i < lock_count; i++) {
|
||||
if (!(fds[2 + i].revents & (POLLHUP | POLLERR)))
|
||||
@@ -392,7 +420,8 @@ int main(int argc, char **argv) {
|
||||
if (argc == 3 && !strcmp(argv[1], "--fake-logind"))
|
||||
return serve(argv[2]);
|
||||
|
||||
snprintf(tmpdir, sizeof(tmpdir), "/tmp/riot-logind-sleep-XXXXXX");
|
||||
const char *temporary = getenv("TMPDIR");
|
||||
snprintf(tmpdir, sizeof(tmpdir), "%s/riot-logind-sleep-XXXXXX", temporary ? temporary : "/tmp");
|
||||
if (!mkdtemp(tmpdir)) { perror("mkdtemp"); return 2; }
|
||||
snprintf(records, sizeof(records), "%s/records", tmpdir);
|
||||
fclose(fopen(records, "w"));
|
||||
@@ -591,6 +620,57 @@ int main(int argc, char **argv) {
|
||||
iokitd_logind_stop(l);
|
||||
l = NULL;
|
||||
|
||||
/* Blocking assertions use idle/block, independently from sleep/delay. */
|
||||
setenv("DBUS_SYSTEM_BUS_ADDRESS", bus_address, 1);
|
||||
setenv("DBUS_SESSION_BUS_ADDRESS", bus_address, 1);
|
||||
api = native_api(0);
|
||||
struct iokitd_power_inhibit *inhibit = NULL;
|
||||
int lock_count_before = count_prefix("Inhibit\t");
|
||||
int release_count_before = count_prefix("Released\t");
|
||||
r = iokitd_power_inhibit_create(&api, 0, &inhibit);
|
||||
CHECK(r == 0 && inhibit, "idle inhibit: create %d", r);
|
||||
CHECK(wait_prefix("Inhibit\t", lock_count_before + 1, 1000), "idle inhibit: not called");
|
||||
CHECK(strstr(read_records(), "\tidle\tDarling\tmacOS application prevents automatic idle sleep\tblock") != NULL,
|
||||
"idle inhibit: wrong arguments");
|
||||
iokitd_power_inhibit_destroy(inhibit);
|
||||
inhibit = NULL;
|
||||
CHECK(wait_prefix("Released\t", release_count_before + 1, 1000), "idle inhibit: not released");
|
||||
|
||||
lock_count_before = count_prefix("Inhibit\t");
|
||||
release_count_before = count_prefix("Released\t");
|
||||
r = iokitd_power_inhibit_create(&api, 1, &inhibit);
|
||||
CHECK(r == 0 && inhibit, "display inhibit: create %d", r);
|
||||
CHECK(wait_prefix("ScreenInhibit\t", 1, 1000), "display inhibit: screen not inhibited");
|
||||
CHECK(wait_prefix("Inhibit\t", lock_count_before + 1, 1000), "display inhibit: idle not inhibited");
|
||||
iokitd_power_inhibit_destroy(inhibit);
|
||||
inhibit = NULL;
|
||||
CHECK(wait_prefix("ScreenRelease\t", 1, 1000), "display inhibit: cookie not released");
|
||||
CHECK(wait_prefix("Released\t", release_count_before + 1, 1000), "display inhibit: idle not released");
|
||||
|
||||
command(control[1], 'D');
|
||||
CHECK(wait_prefix("ScreenDeny\t1", 1, 1000), "display denial: fake not ready");
|
||||
release_count_before = count_prefix("Released\t");
|
||||
r = iokitd_power_inhibit_create(&api, 1, &inhibit);
|
||||
CHECK(r < 0 && !inhibit, "display denial: returned success");
|
||||
CHECK(wait_prefix("Released\t", release_count_before + 1, 1000), "display denial: leaked idle lock");
|
||||
command(control[1], 'A');
|
||||
|
||||
char missing_bus[400];
|
||||
snprintf(missing_bus, sizeof(missing_bus), "unix:path=%s/missing", tmpdir);
|
||||
setenv("DBUS_SYSTEM_BUS_ADDRESS", missing_bus, 1);
|
||||
r = iokitd_power_inhibit_create(&api, 0, &inhibit);
|
||||
CHECK(r < 0 && !inhibit, "missing system bus: returned success");
|
||||
setenv("DBUS_SYSTEM_BUS_ADDRESS", bus_address, 1);
|
||||
setenv("DBUS_SESSION_BUS_ADDRESS", missing_bus, 1);
|
||||
release_count_before = count_prefix("Released\t");
|
||||
r = iokitd_power_inhibit_create(&api, 1, &inhibit);
|
||||
CHECK(r < 0 && !inhibit, "missing session bus: returned success");
|
||||
CHECK(wait_prefix("Released\t", release_count_before + 1, 1000), "missing session bus: leaked idle lock");
|
||||
api.close = NULL;
|
||||
r = iokitd_power_inhibit_create(&api, 0, &inhibit);
|
||||
CHECK(r == -ENOSYS && !inhibit, "incomplete inhibitor api: %d", r);
|
||||
iokitd_power_inhibit_destroy(NULL);
|
||||
|
||||
command(control[1], 'Q');
|
||||
close(control[1]);
|
||||
waitpid(fake_pid, NULL, 0);
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/* Standalone guest probe for Chromium's IOPM assertion create/retain/release API.
|
||||
* The host integration fixture advances checkpoints and inspects fake inhibitors.
|
||||
*/
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#include <IOKit/IOReturn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int failures;
|
||||
#define CHECK(c, ...) do { if (!(c)) { failures++; printf("FAIL: "); printf(__VA_ARGS__); puts(""); } } while (0)
|
||||
|
||||
static void checkpoint(const char* name)
|
||||
{
|
||||
if (failures) { printf("POWER_ASSERTION FAIL failures=%d\n", failures); exit(1); }
|
||||
printf("STAGE %s\n", name);
|
||||
fflush(stdout);
|
||||
if (getchar() == EOF) exit(2);
|
||||
}
|
||||
|
||||
static IOPMAssertionID create(CFStringRef type, IOPMAssertionLevel level)
|
||||
{
|
||||
IOPMAssertionID id = kIOPMNullAssertionID;
|
||||
IOReturn result = IOPMAssertionCreateWithName(type, level, CFSTR("Darling assertion regression"), &id);
|
||||
CHECK(result == kIOReturnSuccess && id != kIOPMNullAssertionID, "create assertion: 0x%x id=%u", result, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
static void release(IOPMAssertionID id)
|
||||
{
|
||||
IOReturn result = IOPMAssertionRelease(id);
|
||||
CHECK(result == kIOReturnSuccess, "release assertion %u: 0x%x", id, result);
|
||||
}
|
||||
|
||||
static void level(IOPMAssertionID id, int value)
|
||||
{
|
||||
CFNumberRef number = CFNumberCreate(NULL, kCFNumberIntType, &value);
|
||||
IOReturn result = IOPMAssertionSetProperty(id, kIOPMAssertionLevelKey, number);
|
||||
CFRelease(number);
|
||||
CHECK(result == kIOReturnSuccess, "set assertion level %u to %d: 0x%x", id, value, result);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
if (argc == 2 && !strcmp(argv[1], "--exit-held")) {
|
||||
create(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn);
|
||||
create(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn);
|
||||
checkpoint("orphan-held");
|
||||
_exit(0); /* Deliberately do not release: iokitd must observe process exit. */
|
||||
}
|
||||
|
||||
IOPMAssertionID invalid = kIOPMNullAssertionID;
|
||||
IOReturn error = IOPMAssertionCreateWithName(CFSTR("UnsupportedRegressionType"),
|
||||
kIOPMAssertionLevelOn, CFSTR("unsupported"), &invalid);
|
||||
CHECK(error == kIOReturnUnsupported && invalid == kIOPMNullAssertionID,
|
||||
"unsupported type: 0x%x id=%u", error, invalid);
|
||||
|
||||
CFMutableDictionaryRef timed = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
int seconds = 5;
|
||||
CFNumberRef timeout = CFNumberCreate(NULL, kCFNumberIntType, &seconds);
|
||||
CFDictionarySetValue(timed, kIOPMAssertionTypeKey, kIOPMAssertionTypeNoIdleSleep);
|
||||
CFDictionarySetValue(timed, kIOPMAssertionNameKey, CFSTR("unsupported timeout"));
|
||||
CFDictionarySetValue(timed, kIOPMAssertionTimeoutKey, timeout);
|
||||
invalid = kIOPMNullAssertionID;
|
||||
error = IOPMAssertionCreateWithProperties(timed, &invalid);
|
||||
CHECK(error == kIOReturnUnsupported && invalid == kIOPMNullAssertionID,
|
||||
"unsupported timeout: 0x%x id=%u", error, invalid);
|
||||
CFRelease(timeout);
|
||||
CFRelease(timed);
|
||||
|
||||
IOPMAssertionID legacy = create(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn);
|
||||
IOPMAssertionID modern = create(kIOPMAssertionTypePreventUserIdleSystemSleep, kIOPMAssertionLevelOn);
|
||||
CHECK(legacy != modern, "assertion IDs must be distinct");
|
||||
IOPMAssertionRetain(legacy);
|
||||
release(legacy); /* One retained reference remains. */
|
||||
release(modern);
|
||||
checkpoint("idle-held");
|
||||
release(legacy);
|
||||
CHECK(IOPMAssertionRelease(legacy) == kIOReturnNotFound, "double release not rejected");
|
||||
checkpoint("idle-released");
|
||||
|
||||
legacy = create(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn);
|
||||
modern = create(kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn);
|
||||
release(legacy);
|
||||
checkpoint("display-held");
|
||||
level(modern, kIOPMAssertionLevelOff);
|
||||
checkpoint("display-off");
|
||||
level(modern, kIOPMAssertionLevelOn);
|
||||
checkpoint("display-on");
|
||||
release(modern);
|
||||
checkpoint("display-released");
|
||||
|
||||
legacy = create(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOff);
|
||||
checkpoint("idle-off");
|
||||
level(legacy, kIOPMAssertionLevelOn);
|
||||
checkpoint("idle-on");
|
||||
release(legacy);
|
||||
checkpoint("finished");
|
||||
printf("POWER_ASSERTION %s failures=%d\n", failures ? "FAIL" : "PASS", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test IOPM assertions against fake logind/ScreenSaver in a disposable prefix."""
|
||||
import argparse
|
||||
import importlib.util
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
spec = importlib.util.spec_from_file_location("power_fixture", ROOT / "tools/riot-power-integration-check.py")
|
||||
fixture = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(fixture)
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--iokitd", type=Path,
|
||||
default=ROOT / "build/darling-src/build/src/external/iokitd/iokitd")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
directory = Path(tempfile.mkdtemp(prefix="assertion-integration-", dir=ROOT / "build"))
|
||||
prefix = directory / "prefix"
|
||||
print("Fixture:", directory, flush=True)
|
||||
fake = subprocess.Popen([str(ROOT / "build/riot-logind-sleep-check"), "--fake-logind", str(directory)],
|
||||
stdout=subprocess.DEVNULL)
|
||||
env = dict(os.environ, DPREFIX=str(prefix), EGL_PLATFORM="x11")
|
||||
for name in ("DYLD_INSERT_LIBRARIES", "DYLD_LIBRARY_PATH", "DYLD_FRAMEWORK_PATH"):
|
||||
env.pop(name, None)
|
||||
guests = []
|
||||
|
||||
def records():
|
||||
return (directory / "records").read_text()
|
||||
|
||||
def state():
|
||||
locks, screens = {}, set()
|
||||
for line in records().splitlines():
|
||||
fields = line.split("\t")
|
||||
if fields[0] == "Inhibit": locks[int(fields[1])] = (fields[2], fields[-1])
|
||||
elif fields[0] == "Released": locks.pop(int(fields[1]), None)
|
||||
elif fields[0] == "ScreenInhibit": screens.add(int(fields[1]))
|
||||
elif fields[0] == "ScreenRelease": screens.discard(int(fields[1]))
|
||||
blocking = [what for what, mode in locks.values() if mode == "block"]
|
||||
assert all(what == "idle" for what in blocking), "assertions must not block manual suspend"
|
||||
return len(blocking), len(screens)
|
||||
|
||||
def expect_state(idle, screens):
|
||||
try:
|
||||
fixture.wait_for(lambda: state() == (idle, screens), f"inhibitors idle={idle} screen={screens}")
|
||||
except RuntimeError:
|
||||
print(records(), flush=True)
|
||||
raise
|
||||
|
||||
def probe(*arguments):
|
||||
guest = fixture.Guest(env, executable=ROOT / "build/riot-power-assertion-check", arguments=arguments)
|
||||
guests.append(guest)
|
||||
return guest
|
||||
|
||||
def stage(guest, label):
|
||||
def arrived():
|
||||
output = guest.read()
|
||||
if "FAIL:" in output or guest.process.poll() is not None:
|
||||
raise RuntimeError("assertion guest failed:\n" + output[-1800:])
|
||||
return re.search(rf"(?m)^STAGE {re.escape(label)}\r?$", output) is not None
|
||||
fixture.wait_for(arrived, "assertion stage " + label)
|
||||
|
||||
def advance(guest):
|
||||
os.write(guest.master, b"\n")
|
||||
|
||||
def finished(guest, marker=None):
|
||||
def exited():
|
||||
guest.read()
|
||||
return guest.process.poll() is not None
|
||||
fixture.wait_for(exited, "assertion guest exit")
|
||||
assert guest.process.returncode == 0, guest.output[-1800:]
|
||||
if marker: assert marker in guest.output, guest.output[-1800:]
|
||||
|
||||
try:
|
||||
fixture.wait_for(lambda: (directory / "address").exists(), "fake power services")
|
||||
address = (directory / "address").read_text().strip()
|
||||
env["DBUS_SYSTEM_BUS_ADDRESS"] = address
|
||||
env["DBUS_SESSION_BUS_ADDRESS"] = address
|
||||
subprocess.run(["darling", "shell", "true"], cwd="/tmp", env=env,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=45, check=True)
|
||||
fixture.stop_fixture_prefix(env)
|
||||
(prefix / "usr/sbin").mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(args.iokitd, prefix / "usr/sbin/iokitd")
|
||||
previous_locks = records().count("Inhibit\t")
|
||||
anchor = fixture.Guest(env)
|
||||
guests.append(anchor)
|
||||
fixture.wait_for(anchor.ready, "anchor shell", timeout=40)
|
||||
fixture.wait_for(lambda: records().count("Inhibit\t") > previous_locks, "anchor power monitor")
|
||||
|
||||
main_guest = probe()
|
||||
stage(main_guest, "idle-held")
|
||||
expect_state(1, 0)
|
||||
print("POWER_ASSERTION_INTEGRATION aliases_and_retain=PASS", flush=True)
|
||||
|
||||
worker = probe("--exit-held")
|
||||
stage(worker, "orphan-held")
|
||||
expect_state(2, 1) # Both applications share the system-idle backend.
|
||||
advance(worker)
|
||||
finished(worker)
|
||||
expect_state(1, 0) # Dead child's display assertion is removed; parent stays held.
|
||||
print("POWER_ASSERTION_INTEGRATION process_exit=PASS", flush=True)
|
||||
|
||||
advance(main_guest)
|
||||
stage(main_guest, "idle-released")
|
||||
expect_state(0, 0)
|
||||
print("POWER_ASSERTION_INTEGRATION release=PASS", flush=True)
|
||||
|
||||
for label, idle, screens in [("display-held", 1, 1), ("display-off", 0, 0),
|
||||
("display-on", 1, 1), ("display-released", 0, 0),
|
||||
("idle-off", 0, 0), ("idle-on", 1, 0), ("finished", 0, 0)]:
|
||||
advance(main_guest)
|
||||
stage(main_guest, label)
|
||||
expect_state(idle, screens)
|
||||
advance(main_guest)
|
||||
finished(main_guest, "POWER_ASSERTION PASS failures=0")
|
||||
print("POWER_ASSERTION_INTEGRATION display_and_levels=PASS", flush=True)
|
||||
print("POWER_ASSERTION_INTEGRATION PASS", flush=True)
|
||||
finally:
|
||||
fixture.stop_fixture_prefix(env)
|
||||
for guest in guests: guest.close()
|
||||
try:
|
||||
control = os.open(directory / "control", os.O_WRONLY | os.O_NONBLOCK)
|
||||
os.write(control, b"Q")
|
||||
os.close(control)
|
||||
except OSError:
|
||||
pass
|
||||
try: fake.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
fake.terminate()
|
||||
fake.wait(timeout=5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -22,7 +22,7 @@ ROOT = Path(__file__).resolve().parent.parent
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--open-workaround", type=Path,
|
||||
help="development-only guest interposer for testing with an older server")
|
||||
args = parser.parse_args()
|
||||
args = argparse.Namespace(open_workaround=None)
|
||||
|
||||
|
||||
def wait_for(predicate, description, timeout=15):
|
||||
@@ -34,18 +34,46 @@ def wait_for(predicate, description, timeout=15):
|
||||
raise RuntimeError("timed out: " + description)
|
||||
|
||||
|
||||
def stop_fixture_prefix(env):
|
||||
"""Stop only this fixture's prefix and wait out Darling's asynchronous kill."""
|
||||
pid_file = Path(env["DPREFIX"]) / ".init.pid"
|
||||
try:
|
||||
pid_text = pid_file.read_text().strip()
|
||||
pid = int(pid_text)
|
||||
except (OSError, ValueError):
|
||||
pid_text, pid = None, None
|
||||
subprocess.run(["darling", "shutdown"], env=env, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL, timeout=10)
|
||||
if pid is not None:
|
||||
def exited():
|
||||
try:
|
||||
return Path(f"/proc/{pid}/stat").read_text().rsplit(")", 1)[1].split()[0] == "Z"
|
||||
except FileNotFoundError:
|
||||
return True
|
||||
wait_for(exited, "fixture Darling shutdown", timeout=10)
|
||||
# getInitProcess treats a zombie PID as live, although its mount namespace
|
||||
# is already gone. Forget that exited PID before the next fixture launch.
|
||||
try:
|
||||
if pid_file.read_text().strip() == pid_text:
|
||||
pid_file.unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
class Guest:
|
||||
def __init__(self, env, delay):
|
||||
def __init__(self, env, delay=None, *, executable=None, arguments=()):
|
||||
master, slave = pty.openpty()
|
||||
self.master = master
|
||||
self.anchor = delay is None
|
||||
self.anchor = delay is None and executable is None
|
||||
self.output = ""
|
||||
self.log_path = Path(env["DPREFIX"]).parent / f"guest-{time.monotonic_ns()}.log"
|
||||
command = ["darling", "shell"]
|
||||
if args.open_workaround:
|
||||
command += ["env", "DYLD_INSERT_LIBRARIES=/Volumes/SystemRoot" +
|
||||
str(args.open_workaround.resolve())]
|
||||
if delay is None:
|
||||
if executable is not None:
|
||||
command += ["/Volumes/SystemRoot" + str(Path(executable).resolve()), *arguments]
|
||||
elif delay is None:
|
||||
command += ["sh", "-c", "printf 'ANCHOR_READY\\n'; exec sleep 120"]
|
||||
else:
|
||||
command += ["/Volumes/SystemRoot" + str(ROOT / "build/riot-power-notify-check"), str(delay), "30"]
|
||||
@@ -105,6 +133,8 @@ class Guest:
|
||||
# Darling creates a root-owned overlay workdir. Keep fixture files as diagnostics;
|
||||
# recursive deletion without root would mask the actual test result.
|
||||
def main():
|
||||
global args
|
||||
args = parser.parse_args()
|
||||
directory = Path(tempfile.mkdtemp(prefix="power-integration-", dir=ROOT / "build"))
|
||||
temp = str(directory)
|
||||
print("Fixture:", directory, flush=True)
|
||||
@@ -130,8 +160,7 @@ def main():
|
||||
# Let the launcher initialize the empty prefix before adding overrides.
|
||||
subprocess.run(["darling", "shell", "true"], cwd="/tmp", env=env,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=45, check=True)
|
||||
subprocess.run(["darling", "shutdown"], env=env, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL, timeout=10, check=True)
|
||||
stop_fixture_prefix(env)
|
||||
(prefix / "usr/sbin").mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(ROOT / "build/darling-src/build/src/external/iokitd/iokitd", prefix / "usr/sbin/iokitd")
|
||||
|
||||
@@ -211,8 +240,7 @@ def main():
|
||||
print("POWER_INTEGRATION no_clients=PASS", flush=True)
|
||||
print("POWER_INTEGRATION PASS" + (" (empty-OOL test workaround enabled)" if args.open_workaround else ""))
|
||||
finally:
|
||||
subprocess.run(["darling", "shutdown"], env=env, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL, timeout=10)
|
||||
stop_fixture_prefix(env)
|
||||
for guest in guests:
|
||||
guest.close()
|
||||
if (directory / "control").exists():
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
"revision": "5549ac4cd2db923c256f016a1381b3bfb716730a",
|
||||
"repository": "https://github.com/darlinghq/darling-iokitd.git",
|
||||
"patch": "patches/iokitd.patch",
|
||||
"modified_files": 12,
|
||||
"modified_files": 16,
|
||||
"new_files": [
|
||||
"src/IOInterestNotification.h",
|
||||
"src/IOInterestNotification.mm",
|
||||
@@ -134,7 +134,9 @@
|
||||
"src/IOPlatformExpertDevice.mm",
|
||||
"src/LogindSleep.c",
|
||||
"src/LogindSleep.h",
|
||||
"src/PowerSources.mm"
|
||||
"src/PowerSources.mm",
|
||||
"src/HostPowerInhibit.c",
|
||||
"src/HostPowerInhibit.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user