]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2a66c2a1 LP |
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 | ||
75db809a | 47 | static BrightnessWriter* brightness_writer_free(BrightnessWriter *w) { |
2a66c2a1 | 48 | if (!w) |
75db809a | 49 | return NULL; |
2a66c2a1 LP |
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 | ||
75db809a | 62 | return mfree(w); |
2a66c2a1 LP |
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) { | |
99534007 | 99 | BrightnessWriter *w = ASSERT_PTR(userdata); |
2a66c2a1 LP |
100 | int r; |
101 | ||
102 | assert(s); | |
103 | assert(si); | |
2a66c2a1 LP |
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 | ||
7e0ed2e9 | 139 | r = safe_fork("(sd-bright)", FORK_DEATHSIG|FORK_NULL_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_REOPEN_LOG, &w->child); |
2a66c2a1 LP |
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 | ||
de7fef4b | 176 | r = set_ensure_put(set, &bus_message_hash_ops, message); |
648c339c | 177 | if (r <= 0) |
2a66c2a1 | 178 | return r; |
2a66c2a1 | 179 | sd_bus_message_ref(message); |
de7fef4b | 180 | |
2a66c2a1 LP |
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 | ||
f7f9c69a | 211 | /* We override any previously requested brightness here: we coalesce writes, and the newest |
2a66c2a1 LP |
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 | ||
2a66c2a1 LP |
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 | ||
9a8d1b45 SS |
232 | r = hashmap_ensure_put(&m->brightness_writers, &brightness_writer_hash_ops, w->path, w); |
233 | if (r == -ENOMEM) | |
234 | return log_oom(); | |
2a66c2a1 LP |
235 | if (r < 0) |
236 | return log_error_errno(r, "Failed to add brightness writer to hashmap: %m"); | |
9a8d1b45 | 237 | |
2a66c2a1 LP |
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 | } |