1 /* SPDX-License-Identifier: LGPL-2.1+ */
4 #include "device-util.h"
5 #include "hash-funcs.h"
6 #include "logind-brightness.h"
8 #include "process-util.h"
9 #include "stdio-util.h"
11 /* Brightness and LED devices tend to be very slow to write to (often being I2C and such). Writes to the
12 * sysfs attributes are synchronous, and hence will freeze our process on access. We can't really have that,
13 * hence we add some complexity: whenever we need to write to the brightness attribute, we do so in a forked
14 * off process, which terminates when it is done. Watching that process allows us to watch completion of the
17 * To make this even more complex: clients are likely to send us many write requests in a short time-frame
18 * (because they implement reactive brightness sliders on screen). Let's coalesce writes to make this
19 * efficient: whenever we get requests to change brightness while we are still writing to the brightness
20 * attribute, let's remember the request and restart a new one when the initial operation finished. When we
21 * get another request while one is ongoing and one is pending we'll replace the pending one with the new
24 * The bus messages are answered when the first write operation finishes that started either due to the
25 * request or due to a later request that overrode the requested one.
27 * Yes, this is complex, but I don't see an easier way if we want to be both efficient and still support
28 * completion notification. */
30 typedef struct BrightnessWriter
{
41 Set
*current_messages
;
42 Set
*pending_messages
;
44 sd_event_source
* child_event_source
;
47 static void brightness_writer_free(BrightnessWriter
*w
) {
51 if (w
->manager
&& w
->path
)
52 (void) hashmap_remove_value(w
->manager
->brightness_writers
, w
->path
, w
);
54 sd_device_unref(w
->device
);
57 set_free(w
->current_messages
);
58 set_free(w
->pending_messages
);
60 w
->child_event_source
= sd_event_source_unref(w
->child_event_source
);
65 DEFINE_TRIVIAL_CLEANUP_FUNC(BrightnessWriter
*, brightness_writer_free
);
67 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
68 brightness_writer_hash_ops
,
73 brightness_writer_free
);
75 static void brightness_writer_reply(BrightnessWriter
*w
, int error
) {
81 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
83 m
= set_steal_first(w
->current_messages
);
88 r
= sd_bus_reply_method_return(m
, NULL
);
90 r
= sd_bus_reply_method_errnof(m
, error
, "Failed to write to brightness device: %m");
92 log_warning_errno(r
, "Failed to send method reply, ignoring: %m");
96 static int brightness_writer_fork(BrightnessWriter
*w
);
98 static int on_brightness_writer_exit(sd_event_source
*s
, const siginfo_t
*si
, void *userdata
) {
99 BrightnessWriter
*w
= userdata
;
106 assert(si
->si_pid
== w
->child
);
108 w
->child_event_source
= sd_event_source_unref(w
->child_event_source
);
110 brightness_writer_reply(w
,
111 si
->si_code
== CLD_EXITED
&&
112 si
->si_status
== EXIT_SUCCESS
? 0 : -EPROTO
);
115 /* Another request to change the brightness has been queued. Act on it, but make the pending
116 * messages the current ones. */
118 set_free(w
->current_messages
);
119 w
->current_messages
= TAKE_PTR(w
->pending_messages
);
121 r
= brightness_writer_fork(w
);
125 brightness_writer_reply(w
, r
);
128 brightness_writer_free(w
);
132 static int brightness_writer_fork(BrightnessWriter
*w
) {
137 assert(w
->child
== 0);
138 assert(!w
->child_event_source
);
140 r
= safe_fork("(sd-bright)", FORK_DEATHSIG
|FORK_NULL_STDIO
|FORK_CLOSE_ALL_FDS
|FORK_LOG
, &w
->child
);
144 char brs
[DECIMAL_STR_MAX(uint32_t)+1];
147 xsprintf(brs
, "%" PRIu32
, w
->brightness
);
149 r
= sd_device_set_sysattr_value(w
->device
, "brightness", brs
);
151 log_device_error_errno(w
->device
, r
, "Failed to write brightness to device: %m");
158 r
= sd_event_add_child(w
->manager
->event
, &w
->child_event_source
, w
->child
, WEXITED
, on_brightness_writer_exit
, w
);
160 return log_error_errno(r
, "Failed to watch brightness writer child " PID_FMT
": %m", w
->child
);
165 static int set_add_message(Set
**set
, sd_bus_message
*message
) {
173 r
= sd_bus_message_get_expect_reply(message
);
177 r
= set_ensure_put(set
, &bus_message_hash_ops
, message
);
181 sd_bus_message_ref(message
);
186 int manager_write_brightness(
190 sd_bus_message
*message
) {
192 _cleanup_(brightness_writer_freep
) BrightnessWriter
*w
= NULL
;
193 BrightnessWriter
*existing
;
200 r
= sd_device_get_syspath(device
, &path
);
202 return log_device_error_errno(device
, r
, "Failed to get sysfs path for brightness device: %m");
204 existing
= hashmap_get(m
->brightness_writers
, path
);
206 /* There's already a writer for this device. Let's update it with the new brightness, and add
207 * our message to the set of message to reply when done. */
209 r
= set_add_message(&existing
->pending_messages
, message
);
211 return log_error_errno(r
, "Failed to add message to set: %m");
213 /* We override any previously requested brightness here: we coalesce writes, and the newest
214 * requested brightness is the one we'll put into effect. */
215 existing
->brightness
= brightness
;
216 existing
->again
= true; /* request another iteration of the writer when the current one is
221 r
= hashmap_ensure_allocated(&m
->brightness_writers
, &brightness_writer_hash_ops
);
225 w
= new(BrightnessWriter
, 1);
229 *w
= (BrightnessWriter
) {
230 .device
= sd_device_ref(device
),
231 .path
= strdup(path
),
232 .brightness
= brightness
,
238 r
= hashmap_put(m
->brightness_writers
, w
->path
, w
);
240 return log_error_errno(r
, "Failed to add brightness writer to hashmap: %m");
243 r
= set_add_message(&w
->current_messages
, message
);
245 return log_error_errno(r
, "Failed to add message to set: %m");
247 r
= brightness_writer_fork(w
);