SD_BUS_ERROR_MAP(BUS_ERROR_OPERATION_IN_PROGRESS, EINPROGRESS),
SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP),
SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_BUSY, EBUSY),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NOT_YOUR_DEVICE, EPERM),
SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_NTP_SUPPORT, EOPNOTSUPP),
#define BUS_ERROR_OPERATION_IN_PROGRESS "org.freedesktop.login1.OperationInProgress"
#define BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED "org.freedesktop.login1.SleepVerbNotSupported"
#define BUS_ERROR_SESSION_BUSY "org.freedesktop.login1.SessionBusy"
+#define BUS_ERROR_NOT_YOUR_DEVICE "org.freedesktop.login1.NotYourDevice"
#define BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED "org.freedesktop.timedate1.AutomaticTimeSyncEnabled"
#define BUS_ERROR_NO_NTP_SUPPORT "org.freedesktop.timedate1.NoNTPSupport"
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "bus-util.h"
+#include "device-util.h"
+#include "hash-funcs.h"
+#include "logind-brightness.h"
+#include "logind.h"
+#include "process-util.h"
+#include "stdio-util.h"
+
+/* Brightness and LED devices tend to be very slow to write to (often being I2C and such). Writes to the
+ * sysfs attributes are synchronous, and hence will freeze our process on access. We can't really have that,
+ * hence we add some complexity: whenever we need to write to the brightness attribute, we do so in a forked
+ * off process, which terminates when it is done. Watching that process allows us to watch completion of the
+ * write operation.
+ *
+ * To make this even more complex: clients are likely to send us many write requests in a short time-frame
+ * (because they implement reactive brightness sliders on screen). Let's coalesce writes to make this
+ * efficient: whenever we get requests to change brightness while we are still writing to the brightness
+ * attribute, let's remember the request and restart a new one when the initial operation finished. When we
+ * get another request while one is ongoing and one is pending we'll replace the pending one with the new
+ * one.
+ *
+ * The bus messages are answered when the first write operation finishes that started either due to the
+ * request or due to a later request that overrode the requested one.
+ *
+ * Yes, this is complex, but I don't see an easier way if we want to be both efficient and still support
+ * completion notification. */
+
+typedef struct BrightnessWriter {
+ Manager *manager;
+
+ sd_device *device;
+ char *path;
+
+ pid_t child;
+
+ uint32_t brightness;
+ bool again;
+
+ Set *current_messages;
+ Set *pending_messages;
+
+ sd_event_source* child_event_source;
+} BrightnessWriter;
+
+static void brightness_writer_free(BrightnessWriter *w) {
+ if (!w)
+ return;
+
+ if (w->manager && w->path)
+ (void) hashmap_remove_value(w->manager->brightness_writers, w->path, w);
+
+ sd_device_unref(w->device);
+ free(w->path);
+
+ set_free(w->current_messages);
+ set_free(w->pending_messages);
+
+ w->child_event_source = sd_event_source_unref(w->child_event_source);
+
+ free(w);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(BrightnessWriter*, brightness_writer_free);
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ brightness_writer_hash_ops,
+ char,
+ string_hash_func,
+ string_compare_func,
+ BrightnessWriter,
+ brightness_writer_free);
+
+static void brightness_writer_reply(BrightnessWriter *w, int error) {
+ int r;
+
+ assert(w);
+
+ for (;;) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+ m = set_steal_first(w->current_messages);
+ if (!m)
+ break;
+
+ if (error == 0)
+ r = sd_bus_reply_method_return(m, NULL);
+ else
+ r = sd_bus_reply_method_errnof(m, error, "Failed to write to brightness device: %m");
+ if (r < 0)
+ log_warning_errno(r, "Failed to send method reply, ignoring: %m");
+ }
+}
+
+static int brightness_writer_fork(BrightnessWriter *w);
+
+static int on_brightness_writer_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
+ BrightnessWriter *w = userdata;
+ int r;
+
+ assert(s);
+ assert(si);
+ assert(w);
+
+ assert(si->si_pid == w->child);
+ w->child = 0;
+ w->child_event_source = sd_event_source_unref(w->child_event_source);
+
+ brightness_writer_reply(w,
+ si->si_code == CLD_EXITED &&
+ si->si_status == EXIT_SUCCESS ? 0 : -EPROTO);
+
+ if (w->again) {
+ /* Another request to change the brightness has been queued. Act on it, but make the pending
+ * messages the current ones. */
+ w->again = false;
+ set_free(w->current_messages);
+ w->current_messages = TAKE_PTR(w->pending_messages);
+
+ r = brightness_writer_fork(w);
+ if (r >= 0)
+ return 0;
+
+ brightness_writer_reply(w, r);
+ }
+
+ brightness_writer_free(w);
+ return 0;
+}
+
+static int brightness_writer_fork(BrightnessWriter *w) {
+ int r;
+
+ assert(w);
+ assert(w->manager);
+ assert(w->child == 0);
+ assert(!w->child_event_source);
+
+ r = safe_fork("(sd-bright)", FORK_DEATHSIG|FORK_NULL_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG, &w->child);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ char brs[DECIMAL_STR_MAX(uint32_t)+1];
+
+ /* Child */
+ xsprintf(brs, "%" PRIu32, w->brightness);
+
+ r = sd_device_set_sysattr_value(w->device, "brightness", brs);
+ if (r < 0) {
+ log_device_error_errno(w->device, r, "Failed to write brightness to device: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ r = sd_event_add_child(w->manager->event, &w->child_event_source, w->child, WEXITED, on_brightness_writer_exit, w);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch brightness writer child " PID_FMT ": %m", w->child);
+
+ return 0;
+}
+
+static int set_add_message(Set **set, sd_bus_message *message) {
+ int r;
+
+ assert(set);
+
+ if (!message)
+ return 0;
+
+ r = sd_bus_message_get_expect_reply(message);
+ if (r <= 0)
+ return r;
+
+ r = set_ensure_allocated(set, &bus_message_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = set_put(*set, message);
+ if (r < 0)
+ return r;
+
+ sd_bus_message_ref(message);
+ return 1;
+}
+
+int manager_write_brightness(
+ Manager *m,
+ sd_device *device,
+ uint32_t brightness,
+ sd_bus_message *message) {
+
+ _cleanup_(brightness_writer_freep) BrightnessWriter *w = NULL;
+ BrightnessWriter *existing;
+ const char *path;
+ int r;
+
+ assert(m);
+ assert(device);
+
+ r = sd_device_get_syspath(device, &path);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to get sysfs path for brightness device: %m");
+
+ existing = hashmap_get(m->brightness_writers, path);
+ if (existing) {
+ /* There's already a writer for this device. Let's update it with the new brightness, and add
+ * our message to the set of message to reply when done. */
+
+ r = set_add_message(&existing->pending_messages, message);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add message to set: %m");
+
+ /* We overide any previously requested brightness here: we coalesce writes, and the newest
+ * requested brightness is the one we'll put into effect. */
+ existing->brightness = brightness;
+ existing->again = true; /* request another iteration of the writer when the current one is
+ * complete */
+ return 0;
+ }
+
+ r = hashmap_ensure_allocated(&m->brightness_writers, &brightness_writer_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ w = new(BrightnessWriter, 1);
+ if (!w)
+ return log_oom();
+
+ *w = (BrightnessWriter) {
+ .device = sd_device_ref(device),
+ .path = strdup(path),
+ .brightness = brightness,
+ };
+
+ if (!w->path)
+ return log_oom();
+
+ r = hashmap_put(m->brightness_writers, w->path, w);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add brightness writer to hashmap: %m");
+ w->manager = m;
+
+ r = set_add_message(&w->current_messages, message);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add message to set: %m");
+
+ r = brightness_writer_fork(w);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(w);
+ return 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-bus.h"
+#include "sd-device.h"
+
+#include "logind.h"
+
+int manager_write_brightness(Manager *m, sd_device *device, uint32_t brightness, sd_bus_message *message);
#include "bus-label.h"
#include "bus-util.h"
#include "fd-util.h"
+#include "logind-brightness.h"
#include "logind-session-device.h"
#include "logind-session.h"
#include "logind.h"
#include "missing_capability.h"
+#include "path-util.h"
#include "signal-util.h"
#include "stat-util.h"
#include "strv.h"
return sd_bus_reply_method_return(message, NULL);
}
+static int method_set_brightness(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+ const char *subsystem, *name, *seat;
+ Session *s = userdata;
+ uint32_t brightness;
+ uid_t uid;
+ int r;
+
+ assert(message);
+ assert(s);
+
+ r = sd_bus_message_read(message, "ssu", &subsystem, &name, &brightness);
+ if (r < 0)
+ return r;
+
+ if (!STR_IN_SET(subsystem, "backlight", "leds"))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Subsystem type %s not supported, must be one of 'backlight' or 'leds'.", subsystem);
+ if (!filename_is_valid(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not a valid device name %s, refusing.", name);
+
+ if (!s->seat)
+ return sd_bus_error_setf(error, BUS_ERROR_NOT_YOUR_DEVICE, "Your session has no seat, refusing.");
+ if (s->seat->active != s)
+ return sd_bus_error_setf(error, BUS_ERROR_NOT_YOUR_DEVICE, "Session is not in foreground, refusing.");
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_euid(creds, &uid);
+ if (r < 0)
+ return r;
+
+ if (uid != 0 && uid != s->user->uid)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may change brightness.");
+
+ r = sd_device_new_from_subsystem_sysname(&d, subsystem, name);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to open device %s:%s: %m", subsystem, name);
+
+ if (sd_device_get_property_value(d, "ID_SEAT", &seat) >= 0 && !streq_ptr(seat, s->seat->id))
+ return sd_bus_error_setf(error, BUS_ERROR_NOT_YOUR_DEVICE, "Device %s:%s does not belong to your seat %s, refusing.", subsystem, name, s->seat->id);
+
+ r = manager_write_brightness(s->manager, d, brightness, message);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
const sd_bus_vtable session_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("TakeDevice", "uu", "hb", method_take_device, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ReleaseDevice", "uu", NULL, method_release_device, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("PauseDeviceComplete", "uu", NULL, method_pause_device_complete, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("SetBrightness", "ssu", NULL, method_set_brightness, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("PauseDevice", "uus", 0),
SD_BUS_SIGNAL("ResumeDevice", "uuh", 0),
hashmap_free(m->users);
hashmap_free(m->inhibitors);
hashmap_free(m->buttons);
+ hashmap_free(m->brightness_writers);
hashmap_free(m->user_units);
hashmap_free(m->session_units);
(void) mkdir_label("/run/systemd/users", 0755);
(void) mkdir_label("/run/systemd/sessions", 0755);
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, -1) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, SIGCHLD, -1) >= 0);
r = manager_new(&m);
if (r < 0)
Hashmap *users;
Hashmap *inhibitors;
Hashmap *buttons;
+ Hashmap *brightness_writers;
LIST_HEAD(Seat, seat_gc_queue);
LIST_HEAD(Session, session_gc_queue);
command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@'])
liblogind_core_sources = files('''
+ logind-acl.h
+ logind-action.c
+ logind-action.h
+ logind-brightness.c
+ logind-brightness.h
+ logind-button.c
+ logind-button.h
logind-core.c
+ logind-dbus.c
logind-device.c
logind-device.h
- logind-button.c
- logind-button.h
- logind-action.c
- logind-action.h
+ logind-inhibit.c
+ logind-inhibit.h
+ logind-seat-dbus.c
logind-seat.c
logind-seat.h
- logind-session.c
- logind-session.h
+ logind-session-dbus.c
logind-session-device.c
logind-session-device.h
+ logind-session.c
+ logind-session.h
+ logind-user-dbus.c
logind-user.c
logind-user.h
- logind-inhibit.c
- logind-inhibit.h
- logind-dbus.c
- logind-session-dbus.c
- logind-seat-dbus.c
- logind-user-dbus.c
logind-utmp.c
- logind-acl.h
'''.split())
liblogind_core_sources += [logind_gperf_c]
send_interface="org.freedesktop.login1.Session"
send_member="PauseDeviceComplete"/>
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="SetBrightness"/>
+
<allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.User"
send_member="Terminate"/>
return sd_bus_send(NULL, reply, NULL);
}
+
+static void bus_message_unref_wrapper(void *m) {
+ sd_bus_message_unref(m);
+}
+
+const struct hash_ops bus_message_hash_ops = {
+ .hash = trivial_hash_func,
+ .compare = trivial_compare_func,
+ .free_value = bus_message_unref_wrapper,
+};
}
int bus_reply_pair_array(sd_bus_message *m, char **l);
+
+extern const struct hash_ops bus_message_hash_ops;