]>
Commit | Line | Data |
---|---|---|
2a66c2a1 LP |
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 | static void brightness_writer_free(BrightnessWriter *w) { | |
48 | if (!w) | |
49 | return; | |
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 | free(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 = 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 | ||
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, &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 | ||
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 | ||
de7fef4b | 177 | r = set_ensure_put(set, &bus_message_hash_ops, message); |
648c339c | 178 | if (r <= 0) |
2a66c2a1 | 179 | return r; |
2a66c2a1 | 180 | sd_bus_message_ref(message); |
de7fef4b | 181 | |
2a66c2a1 LP |
182 | return 1; |
183 | } | |
184 | ||
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 | ||
f7f9c69a | 212 | /* We override any previously requested brightness here: we coalesce writes, and the newest |
2a66c2a1 LP |
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 | r = hashmap_ensure_allocated(&m->brightness_writers, &brightness_writer_hash_ops); | |
221 | if (r < 0) | |
222 | return log_oom(); | |
223 | ||
224 | w = new(BrightnessWriter, 1); | |
225 | if (!w) | |
226 | return log_oom(); | |
227 | ||
228 | *w = (BrightnessWriter) { | |
229 | .device = sd_device_ref(device), | |
230 | .path = strdup(path), | |
231 | .brightness = brightness, | |
232 | }; | |
233 | ||
234 | if (!w->path) | |
235 | return log_oom(); | |
236 | ||
237 | r = hashmap_put(m->brightness_writers, w->path, w); | |
238 | if (r < 0) | |
239 | return log_error_errno(r, "Failed to add brightness writer to hashmap: %m"); | |
240 | w->manager = m; | |
241 | ||
242 | r = set_add_message(&w->current_messages, message); | |
243 | if (r < 0) | |
244 | return log_error_errno(r, "Failed to add message to set: %m"); | |
245 | ||
246 | r = brightness_writer_fork(w); | |
247 | if (r < 0) | |
248 | return r; | |
249 | ||
250 | TAKE_PTR(w); | |
251 | return 0; | |
252 | } |