Branch data 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 : : }
|