]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-brightness.c
tree-wide: use set_ensure_put()
[thirdparty/systemd.git] / src / login / logind-brightness.c
CommitLineData
2a66c2a1
LP
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#include "bus-util.h"
4#include "device-util.h"
5#include "hash-funcs.h"
6#include "logind-brightness.h"
7#include "logind.h"
8#include "process-util.h"
9#include "stdio-util.h"
10
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
15 * write operation.
16 *
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
22 * one.
23 *
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.
26 *
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. */
29
30typedef struct BrightnessWriter {
31 Manager *manager;
32
33 sd_device *device;
34 char *path;
35
36 pid_t child;
37
38 uint32_t brightness;
39 bool again;
40
41 Set *current_messages;
42 Set *pending_messages;
43
44 sd_event_source* child_event_source;
45} BrightnessWriter;
46
47static void brightness_writer_free(BrightnessWriter *w) {
48 if (!w)
49 return;
50
51 if (w->manager && w->path)
52 (void) hashmap_remove_value(w->manager->brightness_writers, w->path, w);
53
54 sd_device_unref(w->device);
55 free(w->path);
56
57 set_free(w->current_messages);
58 set_free(w->pending_messages);
59
60 w->child_event_source = sd_event_source_unref(w->child_event_source);
61
62 free(w);
63}
64
65DEFINE_TRIVIAL_CLEANUP_FUNC(BrightnessWriter*, brightness_writer_free);
66
67DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
68 brightness_writer_hash_ops,
69 char,
70 string_hash_func,
71 string_compare_func,
72 BrightnessWriter,
73 brightness_writer_free);
74
75static void brightness_writer_reply(BrightnessWriter *w, int error) {
76 int r;
77
78 assert(w);
79
80 for (;;) {
81 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
82
83 m = set_steal_first(w->current_messages);
84 if (!m)
85 break;
86
87 if (error == 0)
88 r = sd_bus_reply_method_return(m, NULL);
89 else
90 r = sd_bus_reply_method_errnof(m, error, "Failed to write to brightness device: %m");
91 if (r < 0)
92 log_warning_errno(r, "Failed to send method reply, ignoring: %m");
93 }
94}
95
96static int brightness_writer_fork(BrightnessWriter *w);
97
98static int on_brightness_writer_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
99 BrightnessWriter *w = userdata;
100 int r;
101
102 assert(s);
103 assert(si);
104 assert(w);
105
106 assert(si->si_pid == w->child);
107 w->child = 0;
108 w->child_event_source = sd_event_source_unref(w->child_event_source);
109
110 brightness_writer_reply(w,
111 si->si_code == CLD_EXITED &&
112 si->si_status == EXIT_SUCCESS ? 0 : -EPROTO);
113
114 if (w->again) {
115 /* Another request to change the brightness has been queued. Act on it, but make the pending
116 * messages the current ones. */
117 w->again = false;
118 set_free(w->current_messages);
119 w->current_messages = TAKE_PTR(w->pending_messages);
120
121 r = brightness_writer_fork(w);
122 if (r >= 0)
123 return 0;
124
125 brightness_writer_reply(w, r);
126 }
127
128 brightness_writer_free(w);
129 return 0;
130}
131
132static int brightness_writer_fork(BrightnessWriter *w) {
133 int r;
134
135 assert(w);
136 assert(w->manager);
137 assert(w->child == 0);
138 assert(!w->child_event_source);
139
140 r = safe_fork("(sd-bright)", FORK_DEATHSIG|FORK_NULL_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG, &w->child);
141 if (r < 0)
142 return r;
143 if (r == 0) {
144 char brs[DECIMAL_STR_MAX(uint32_t)+1];
145
146 /* Child */
147 xsprintf(brs, "%" PRIu32, w->brightness);
148
149 r = sd_device_set_sysattr_value(w->device, "brightness", brs);
150 if (r < 0) {
151 log_device_error_errno(w->device, r, "Failed to write brightness to device: %m");
152 _exit(EXIT_FAILURE);
153 }
154
155 _exit(EXIT_SUCCESS);
156 }
157
158 r = sd_event_add_child(w->manager->event, &w->child_event_source, w->child, WEXITED, on_brightness_writer_exit, w);
159 if (r < 0)
160 return log_error_errno(r, "Failed to watch brightness writer child " PID_FMT ": %m", w->child);
161
162 return 0;
163}
164
165static int set_add_message(Set **set, sd_bus_message *message) {
166 int r;
167
168 assert(set);
169
170 if (!message)
171 return 0;
172
173 r = sd_bus_message_get_expect_reply(message);
174 if (r <= 0)
175 return r;
176
de7fef4b 177 r = set_ensure_put(set, &bus_message_hash_ops, message);
2a66c2a1
LP
178 if (r < 0)
179 return r;
180
181 sd_bus_message_ref(message);
de7fef4b 182
2a66c2a1
LP
183 return 1;
184}
185
186int manager_write_brightness(
187 Manager *m,
188 sd_device *device,
189 uint32_t brightness,
190 sd_bus_message *message) {
191
192 _cleanup_(brightness_writer_freep) BrightnessWriter *w = NULL;
193 BrightnessWriter *existing;
194 const char *path;
195 int r;
196
197 assert(m);
198 assert(device);
199
200 r = sd_device_get_syspath(device, &path);
201 if (r < 0)
202 return log_device_error_errno(device, r, "Failed to get sysfs path for brightness device: %m");
203
204 existing = hashmap_get(m->brightness_writers, path);
205 if (existing) {
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. */
208
209 r = set_add_message(&existing->pending_messages, message);
210 if (r < 0)
211 return log_error_errno(r, "Failed to add message to set: %m");
212
f7f9c69a 213 /* We override any previously requested brightness here: we coalesce writes, and the newest
2a66c2a1
LP
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
217 * complete */
218 return 0;
219 }
220
221 r = hashmap_ensure_allocated(&m->brightness_writers, &brightness_writer_hash_ops);
222 if (r < 0)
223 return log_oom();
224
225 w = new(BrightnessWriter, 1);
226 if (!w)
227 return log_oom();
228
229 *w = (BrightnessWriter) {
230 .device = sd_device_ref(device),
231 .path = strdup(path),
232 .brightness = brightness,
233 };
234
235 if (!w->path)
236 return log_oom();
237
238 r = hashmap_put(m->brightness_writers, w->path, w);
239 if (r < 0)
240 return log_error_errno(r, "Failed to add brightness writer to hashmap: %m");
241 w->manager = m;
242
243 r = set_add_message(&w->current_messages, message);
244 if (r < 0)
245 return log_error_errno(r, "Failed to add message to set: %m");
246
247 r = brightness_writer_fork(w);
248 if (r < 0)
249 return r;
250
251 TAKE_PTR(w);
252 return 0;
253}