]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-brightness.c
polkit: turn "interactive" flag to polkit APIs into a proper flags field (#31715)
[thirdparty/systemd.git] / src / login / logind-brightness.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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 BrightnessWriter* brightness_writer_free(BrightnessWriter *w) {
48 if (!w)
49 return NULL;
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 return mfree(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 = ASSERT_PTR(userdata);
100 int r;
101
102 assert(s);
103 assert(si);
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
131 static 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
139 r = safe_fork("(sd-bright)", FORK_DEATHSIG_SIGKILL|FORK_REARRANGE_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_REOPEN_LOG, &w->child);
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
164 static 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
176 r = set_ensure_put(set, &bus_message_hash_ops, message);
177 if (r <= 0)
178 return r;
179 sd_bus_message_ref(message);
180
181 return 1;
182 }
183
184 int 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
211 /* We override any previously requested brightness here: we coalesce writes, and the newest
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
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
232 r = hashmap_ensure_put(&m->brightness_writers, &brightness_writer_hash_ops, w->path, w);
233 if (r == -ENOMEM)
234 return log_oom();
235 if (r < 0)
236 return log_error_errno(r, "Failed to add brightness writer to hashmap: %m");
237
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 }