]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-brightness.c
Merge pull request #12753 from jrouleau/fix/hibernate-resume-timeout
[thirdparty/systemd.git] / src / login / logind-brightness.c
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
30 typedef 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
47 static 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
65 DEFINE_TRIVIAL_CLEANUP_FUNC(BrightnessWriter*, brightness_writer_free);
66
67 DEFINE_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
75 static 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
96 static int brightness_writer_fork(BrightnessWriter *w);
97
98 static 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
132 static 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
165 static 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
177 r = set_ensure_allocated(set, &bus_message_hash_ops);
178 if (r < 0)
179 return r;
180
181 r = set_put(*set, message);
182 if (r < 0)
183 return r;
184
185 sd_bus_message_ref(message);
186 return 1;
187 }
188
189 int manager_write_brightness(
190 Manager *m,
191 sd_device *device,
192 uint32_t brightness,
193 sd_bus_message *message) {
194
195 _cleanup_(brightness_writer_freep) BrightnessWriter *w = NULL;
196 BrightnessWriter *existing;
197 const char *path;
198 int r;
199
200 assert(m);
201 assert(device);
202
203 r = sd_device_get_syspath(device, &path);
204 if (r < 0)
205 return log_device_error_errno(device, r, "Failed to get sysfs path for brightness device: %m");
206
207 existing = hashmap_get(m->brightness_writers, path);
208 if (existing) {
209 /* There's already a writer for this device. Let's update it with the new brightness, and add
210 * our message to the set of message to reply when done. */
211
212 r = set_add_message(&existing->pending_messages, message);
213 if (r < 0)
214 return log_error_errno(r, "Failed to add message to set: %m");
215
216 /* We overide any previously requested brightness here: we coalesce writes, and the newest
217 * requested brightness is the one we'll put into effect. */
218 existing->brightness = brightness;
219 existing->again = true; /* request another iteration of the writer when the current one is
220 * complete */
221 return 0;
222 }
223
224 r = hashmap_ensure_allocated(&m->brightness_writers, &brightness_writer_hash_ops);
225 if (r < 0)
226 return log_oom();
227
228 w = new(BrightnessWriter, 1);
229 if (!w)
230 return log_oom();
231
232 *w = (BrightnessWriter) {
233 .device = sd_device_ref(device),
234 .path = strdup(path),
235 .brightness = brightness,
236 };
237
238 if (!w->path)
239 return log_oom();
240
241 r = hashmap_put(m->brightness_writers, w->path, w);
242 if (r < 0)
243 return log_error_errno(r, "Failed to add brightness writer to hashmap: %m");
244 w->manager = m;
245
246 r = set_add_message(&w->current_messages, message);
247 if (r < 0)
248 return log_error_errno(r, "Failed to add message to set: %m");
249
250 r = brightness_writer_fork(w);
251 if (r < 0)
252 return r;
253
254 TAKE_PTR(w);
255 return 0;
256 }