]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-brightness.c
tree-wide: use ASSERT_PTR more
[thirdparty/systemd.git] / src / login / logind-brightness.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2a66c2a1
LP
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
75db809a 47static BrightnessWriter* brightness_writer_free(BrightnessWriter *w) {
2a66c2a1 48 if (!w)
75db809a 49 return NULL;
2a66c2a1
LP
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
75db809a 62 return mfree(w);
2a66c2a1
LP
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) {
99534007 99 BrightnessWriter *w = ASSERT_PTR(userdata);
2a66c2a1
LP
100 int r;
101
102 assert(s);
103 assert(si);
2a66c2a1
LP
104
105 assert(si->si_pid == w->child);
106 w->child = 0;
107 w->child_event_source = sd_event_source_unref(w->child_event_source);
108
109 brightness_writer_reply(w,
110 si->si_code == CLD_EXITED &&
111 si->si_status == EXIT_SUCCESS ? 0 : -EPROTO);
112
113 if (w->again) {
114 /* Another request to change the brightness has been queued. Act on it, but make the pending
115 * messages the current ones. */
116 w->again = false;
117 set_free(w->current_messages);
118 w->current_messages = TAKE_PTR(w->pending_messages);
119
120 r = brightness_writer_fork(w);
121 if (r >= 0)
122 return 0;
123
124 brightness_writer_reply(w, r);
125 }
126
127 brightness_writer_free(w);
128 return 0;
129}
130
131static int brightness_writer_fork(BrightnessWriter *w) {
132 int r;
133
134 assert(w);
135 assert(w->manager);
136 assert(w->child == 0);
137 assert(!w->child_event_source);
138
7e0ed2e9 139 r = safe_fork("(sd-bright)", FORK_DEATHSIG|FORK_NULL_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_REOPEN_LOG, &w->child);
2a66c2a1
LP
140 if (r < 0)
141 return r;
142 if (r == 0) {
143 char brs[DECIMAL_STR_MAX(uint32_t)+1];
144
145 /* Child */
146 xsprintf(brs, "%" PRIu32, w->brightness);
147
148 r = sd_device_set_sysattr_value(w->device, "brightness", brs);
149 if (r < 0) {
150 log_device_error_errno(w->device, r, "Failed to write brightness to device: %m");
151 _exit(EXIT_FAILURE);
152 }
153
154 _exit(EXIT_SUCCESS);
155 }
156
157 r = sd_event_add_child(w->manager->event, &w->child_event_source, w->child, WEXITED, on_brightness_writer_exit, w);
158 if (r < 0)
159 return log_error_errno(r, "Failed to watch brightness writer child " PID_FMT ": %m", w->child);
160
161 return 0;
162}
163
164static int set_add_message(Set **set, sd_bus_message *message) {
165 int r;
166
167 assert(set);
168
169 if (!message)
170 return 0;
171
172 r = sd_bus_message_get_expect_reply(message);
173 if (r <= 0)
174 return r;
175
de7fef4b 176 r = set_ensure_put(set, &bus_message_hash_ops, message);
648c339c 177 if (r <= 0)
2a66c2a1 178 return r;
2a66c2a1 179 sd_bus_message_ref(message);
de7fef4b 180
2a66c2a1
LP
181 return 1;
182}
183
184int manager_write_brightness(
185 Manager *m,
186 sd_device *device,
187 uint32_t brightness,
188 sd_bus_message *message) {
189
190 _cleanup_(brightness_writer_freep) BrightnessWriter *w = NULL;
191 BrightnessWriter *existing;
192 const char *path;
193 int r;
194
195 assert(m);
196 assert(device);
197
198 r = sd_device_get_syspath(device, &path);
199 if (r < 0)
200 return log_device_error_errno(device, r, "Failed to get sysfs path for brightness device: %m");
201
202 existing = hashmap_get(m->brightness_writers, path);
203 if (existing) {
204 /* There's already a writer for this device. Let's update it with the new brightness, and add
205 * our message to the set of message to reply when done. */
206
207 r = set_add_message(&existing->pending_messages, message);
208 if (r < 0)
209 return log_error_errno(r, "Failed to add message to set: %m");
210
f7f9c69a 211 /* We override any previously requested brightness here: we coalesce writes, and the newest
2a66c2a1
LP
212 * requested brightness is the one we'll put into effect. */
213 existing->brightness = brightness;
214 existing->again = true; /* request another iteration of the writer when the current one is
215 * complete */
216 return 0;
217 }
218
2a66c2a1
LP
219 w = new(BrightnessWriter, 1);
220 if (!w)
221 return log_oom();
222
223 *w = (BrightnessWriter) {
224 .device = sd_device_ref(device),
225 .path = strdup(path),
226 .brightness = brightness,
227 };
228
229 if (!w->path)
230 return log_oom();
231
9a8d1b45
SS
232 r = hashmap_ensure_put(&m->brightness_writers, &brightness_writer_hash_ops, w->path, w);
233 if (r == -ENOMEM)
234 return log_oom();
2a66c2a1
LP
235 if (r < 0)
236 return log_error_errno(r, "Failed to add brightness writer to hashmap: %m");
9a8d1b45 237
2a66c2a1
LP
238 w->manager = m;
239
240 r = set_add_message(&w->current_messages, message);
241 if (r < 0)
242 return log_error_errno(r, "Failed to add message to set: %m");
243
244 r = brightness_writer_fork(w);
245 if (r < 0)
246 return r;
247
248 TAKE_PTR(w);
249 return 0;
250}