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