Line data Source code
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 0 : static void brightness_writer_free(BrightnessWriter *w) {
48 0 : if (!w)
49 0 : return;
50 :
51 0 : if (w->manager && w->path)
52 0 : (void) hashmap_remove_value(w->manager->brightness_writers, w->path, w);
53 :
54 0 : sd_device_unref(w->device);
55 0 : free(w->path);
56 :
57 0 : set_free(w->current_messages);
58 0 : set_free(w->pending_messages);
59 :
60 0 : w->child_event_source = sd_event_source_unref(w->child_event_source);
61 :
62 0 : free(w);
63 : }
64 :
65 0 : DEFINE_TRIVIAL_CLEANUP_FUNC(BrightnessWriter*, brightness_writer_free);
66 :
67 0 : 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 0 : static void brightness_writer_reply(BrightnessWriter *w, int error) {
76 : int r;
77 :
78 0 : assert(w);
79 :
80 0 : for (;;) {
81 0 : _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
82 :
83 0 : m = set_steal_first(w->current_messages);
84 0 : if (!m)
85 0 : break;
86 :
87 0 : if (error == 0)
88 0 : r = sd_bus_reply_method_return(m, NULL);
89 : else
90 0 : r = sd_bus_reply_method_errnof(m, error, "Failed to write to brightness device: %m");
91 0 : if (r < 0)
92 0 : log_warning_errno(r, "Failed to send method reply, ignoring: %m");
93 : }
94 0 : }
95 :
96 : static int brightness_writer_fork(BrightnessWriter *w);
97 :
98 0 : static int on_brightness_writer_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
99 0 : BrightnessWriter *w = userdata;
100 : int r;
101 :
102 0 : assert(s);
103 0 : assert(si);
104 0 : assert(w);
105 :
106 0 : assert(si->si_pid == w->child);
107 0 : w->child = 0;
108 0 : w->child_event_source = sd_event_source_unref(w->child_event_source);
109 :
110 0 : brightness_writer_reply(w,
111 0 : si->si_code == CLD_EXITED &&
112 0 : si->si_status == EXIT_SUCCESS ? 0 : -EPROTO);
113 :
114 0 : 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 0 : w->again = false;
118 0 : set_free(w->current_messages);
119 0 : w->current_messages = TAKE_PTR(w->pending_messages);
120 :
121 0 : r = brightness_writer_fork(w);
122 0 : if (r >= 0)
123 0 : return 0;
124 :
125 0 : brightness_writer_reply(w, r);
126 : }
127 :
128 0 : brightness_writer_free(w);
129 0 : return 0;
130 : }
131 :
132 0 : static int brightness_writer_fork(BrightnessWriter *w) {
133 : int r;
134 :
135 0 : assert(w);
136 0 : assert(w->manager);
137 0 : assert(w->child == 0);
138 0 : assert(!w->child_event_source);
139 :
140 0 : r = safe_fork("(sd-bright)", FORK_DEATHSIG|FORK_NULL_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG, &w->child);
141 0 : if (r < 0)
142 0 : return r;
143 0 : if (r == 0) {
144 : char brs[DECIMAL_STR_MAX(uint32_t)+1];
145 :
146 : /* Child */
147 0 : xsprintf(brs, "%" PRIu32, w->brightness);
148 :
149 0 : r = sd_device_set_sysattr_value(w->device, "brightness", brs);
150 0 : if (r < 0) {
151 0 : log_device_error_errno(w->device, r, "Failed to write brightness to device: %m");
152 0 : _exit(EXIT_FAILURE);
153 : }
154 :
155 0 : _exit(EXIT_SUCCESS);
156 : }
157 :
158 0 : r = sd_event_add_child(w->manager->event, &w->child_event_source, w->child, WEXITED, on_brightness_writer_exit, w);
159 0 : if (r < 0)
160 0 : return log_error_errno(r, "Failed to watch brightness writer child " PID_FMT ": %m", w->child);
161 :
162 0 : return 0;
163 : }
164 :
165 0 : static int set_add_message(Set **set, sd_bus_message *message) {
166 : int r;
167 :
168 0 : assert(set);
169 :
170 0 : if (!message)
171 0 : return 0;
172 :
173 0 : r = sd_bus_message_get_expect_reply(message);
174 0 : if (r <= 0)
175 0 : return r;
176 :
177 0 : r = set_ensure_allocated(set, &bus_message_hash_ops);
178 0 : if (r < 0)
179 0 : return r;
180 :
181 0 : r = set_put(*set, message);
182 0 : if (r < 0)
183 0 : return r;
184 :
185 0 : sd_bus_message_ref(message);
186 0 : return 1;
187 : }
188 :
189 0 : int manager_write_brightness(
190 : Manager *m,
191 : sd_device *device,
192 : uint32_t brightness,
193 : sd_bus_message *message) {
194 :
195 0 : _cleanup_(brightness_writer_freep) BrightnessWriter *w = NULL;
196 : BrightnessWriter *existing;
197 : const char *path;
198 : int r;
199 :
200 0 : assert(m);
201 0 : assert(device);
202 :
203 0 : r = sd_device_get_syspath(device, &path);
204 0 : if (r < 0)
205 0 : return log_device_error_errno(device, r, "Failed to get sysfs path for brightness device: %m");
206 :
207 0 : existing = hashmap_get(m->brightness_writers, path);
208 0 : 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 0 : r = set_add_message(&existing->pending_messages, message);
213 0 : if (r < 0)
214 0 : 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 0 : existing->brightness = brightness;
219 0 : existing->again = true; /* request another iteration of the writer when the current one is
220 : * complete */
221 0 : return 0;
222 : }
223 :
224 0 : r = hashmap_ensure_allocated(&m->brightness_writers, &brightness_writer_hash_ops);
225 0 : if (r < 0)
226 0 : return log_oom();
227 :
228 0 : w = new(BrightnessWriter, 1);
229 0 : if (!w)
230 0 : return log_oom();
231 :
232 0 : *w = (BrightnessWriter) {
233 0 : .device = sd_device_ref(device),
234 0 : .path = strdup(path),
235 : .brightness = brightness,
236 : };
237 :
238 0 : if (!w->path)
239 0 : return log_oom();
240 :
241 0 : r = hashmap_put(m->brightness_writers, w->path, w);
242 0 : if (r < 0)
243 0 : return log_error_errno(r, "Failed to add brightness writer to hashmap: %m");
244 0 : w->manager = m;
245 :
246 0 : r = set_add_message(&w->current_messages, message);
247 0 : if (r < 0)
248 0 : return log_error_errno(r, "Failed to add message to set: %m");
249 :
250 0 : r = brightness_writer_fork(w);
251 0 : if (r < 0)
252 0 : return r;
253 :
254 0 : TAKE_PTR(w);
255 0 : return 0;
256 : }
|