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
brightness_writer_free(BrightnessWriter * w)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
brightness_writer_reply(BrightnessWriter * w,int error)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
on_brightness_writer_exit(sd_event_source * s,const siginfo_t * si,void * userdata)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
brightness_writer_fork(BrightnessWriter * w)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|FORK_REOPEN_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
set_add_message(Set ** set,sd_bus_message * message)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_put(set, &bus_message_hash_ops, message);
178 if (r <= 0)
179 return r;
180 sd_bus_message_ref(message);
181
182 return 1;
183 }
184
manager_write_brightness(Manager * m,sd_device * device,uint32_t brightness,sd_bus_message * message)185 int manager_write_brightness(
186 Manager *m,
187 sd_device *device,
188 uint32_t brightness,
189 sd_bus_message *message) {
190
191 _cleanup_(brightness_writer_freep) BrightnessWriter *w = NULL;
192 BrightnessWriter *existing;
193 const char *path;
194 int r;
195
196 assert(m);
197 assert(device);
198
199 r = sd_device_get_syspath(device, &path);
200 if (r < 0)
201 return log_device_error_errno(device, r, "Failed to get sysfs path for brightness device: %m");
202
203 existing = hashmap_get(m->brightness_writers, path);
204 if (existing) {
205 /* There's already a writer for this device. Let's update it with the new brightness, and add
206 * our message to the set of message to reply when done. */
207
208 r = set_add_message(&existing->pending_messages, message);
209 if (r < 0)
210 return log_error_errno(r, "Failed to add message to set: %m");
211
212 /* We override any previously requested brightness here: we coalesce writes, and the newest
213 * requested brightness is the one we'll put into effect. */
214 existing->brightness = brightness;
215 existing->again = true; /* request another iteration of the writer when the current one is
216 * complete */
217 return 0;
218 }
219
220 w = new(BrightnessWriter, 1);
221 if (!w)
222 return log_oom();
223
224 *w = (BrightnessWriter) {
225 .device = sd_device_ref(device),
226 .path = strdup(path),
227 .brightness = brightness,
228 };
229
230 if (!w->path)
231 return log_oom();
232
233 r = hashmap_ensure_put(&m->brightness_writers, &brightness_writer_hash_ops, w->path, w);
234 if (r == -ENOMEM)
235 return log_oom();
236 if (r < 0)
237 return log_error_errno(r, "Failed to add brightness writer to hashmap: %m");
238
239 w->manager = m;
240
241 r = set_add_message(&w->current_messages, message);
242 if (r < 0)
243 return log_error_errno(r, "Failed to add message to set: %m");
244
245 r = brightness_writer_fork(w);
246 if (r < 0)
247 return r;
248
249 TAKE_PTR(w);
250 return 0;
251 }
252