]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/backlight/backlight.c
Fail on unknown (alphanumerical) specifiers
[thirdparty/systemd.git] / src / backlight / backlight.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2013 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include "libudev.h"
22
23 #include "alloc-util.h"
24 #include "def.h"
25 #include "escape.h"
26 #include "fileio.h"
27 #include "mkdir.h"
28 #include "parse-util.h"
29 #include "proc-cmdline.h"
30 #include "string-util.h"
31 #include "udev-util.h"
32 #include "util.h"
33
34 static struct udev_device *find_pci_or_platform_parent(struct udev_device *device) {
35 struct udev_device *parent;
36 const char *subsystem, *sysname;
37
38 assert(device);
39
40 parent = udev_device_get_parent(device);
41 if (!parent)
42 return NULL;
43
44 subsystem = udev_device_get_subsystem(parent);
45 if (!subsystem)
46 return NULL;
47
48 sysname = udev_device_get_sysname(parent);
49 if (!sysname)
50 return NULL;
51
52 if (streq(subsystem, "drm")) {
53 const char *c;
54
55 c = startswith(sysname, "card");
56 if (!c)
57 return NULL;
58
59 c += strspn(c, DIGITS);
60 if (*c == '-') {
61 /* A connector DRM device, let's ignore all but LVDS and eDP! */
62
63 if (!startswith(c, "-LVDS-") &&
64 !startswith(c, "-Embedded DisplayPort-"))
65 return NULL;
66 }
67
68 } else if (streq(subsystem, "pci")) {
69 const char *value;
70
71 value = udev_device_get_sysattr_value(parent, "class");
72 if (value) {
73 unsigned long class = 0;
74
75 if (safe_atolu(value, &class) < 0) {
76 log_warning("Cannot parse PCI class %s of device %s:%s.",
77 value, subsystem, sysname);
78 return NULL;
79 }
80
81 /* Graphics card */
82 if (class == 0x30000)
83 return parent;
84 }
85
86 } else if (streq(subsystem, "platform"))
87 return parent;
88
89 return find_pci_or_platform_parent(parent);
90 }
91
92 static bool same_device(struct udev_device *a, struct udev_device *b) {
93 assert(a);
94 assert(b);
95
96 if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b)))
97 return false;
98
99 if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b)))
100 return false;
101
102 return true;
103 }
104
105 static bool validate_device(struct udev *udev, struct udev_device *device) {
106 _cleanup_udev_enumerate_unref_ struct udev_enumerate *enumerate = NULL;
107 struct udev_list_entry *item = NULL, *first = NULL;
108 struct udev_device *parent;
109 const char *v, *subsystem;
110 int r;
111
112 assert(udev);
113 assert(device);
114
115 /* Verify whether we should actually care for a specific
116 * backlight device. For backlight devices there might be
117 * multiple ways to access the same control: "firmware"
118 * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
119 * "raw" (via the graphics card). In general we should prefer
120 * "firmware" (i.e. ACPI) or "platform" access over "raw"
121 * access, in order not to confuse the BIOS/EC, and
122 * compatibility with possible low-level hotkey handling of
123 * screen brightness. The kernel will already make sure to
124 * expose only one of "firmware" and "platform" for the same
125 * device to userspace. However, we still need to make sure
126 * that we use "raw" only if no "firmware" or "platform"
127 * device for the same device exists. */
128
129 subsystem = udev_device_get_subsystem(device);
130 if (!streq_ptr(subsystem, "backlight"))
131 return true;
132
133 v = udev_device_get_sysattr_value(device, "type");
134 if (!streq_ptr(v, "raw"))
135 return true;
136
137 parent = find_pci_or_platform_parent(device);
138 if (!parent)
139 return true;
140
141 subsystem = udev_device_get_subsystem(parent);
142 if (!subsystem)
143 return true;
144
145 enumerate = udev_enumerate_new(udev);
146 if (!enumerate)
147 return true;
148
149 r = udev_enumerate_add_match_subsystem(enumerate, "backlight");
150 if (r < 0)
151 return true;
152
153 r = udev_enumerate_scan_devices(enumerate);
154 if (r < 0)
155 return true;
156
157 first = udev_enumerate_get_list_entry(enumerate);
158 udev_list_entry_foreach(item, first) {
159 _cleanup_udev_device_unref_ struct udev_device *other;
160 struct udev_device *other_parent;
161 const char *other_subsystem;
162
163 other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
164 if (!other)
165 return true;
166
167 if (same_device(device, other))
168 continue;
169
170 v = udev_device_get_sysattr_value(other, "type");
171 if (!STRPTR_IN_SET(v, "platform", "firmware"))
172 continue;
173
174 /* OK, so there's another backlight device, and it's a
175 * platform or firmware device, so, let's see if we
176 * can verify it belongs to the same device as
177 * ours. */
178 other_parent = find_pci_or_platform_parent(other);
179 if (!other_parent)
180 continue;
181
182 if (same_device(parent, other_parent)) {
183 /* Both have the same PCI parent, that means
184 * we are out. */
185 log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
186 udev_device_get_sysname(device),
187 udev_device_get_sysname(other));
188 return false;
189 }
190
191 other_subsystem = udev_device_get_subsystem(other_parent);
192 if (streq_ptr(other_subsystem, "platform") && streq_ptr(subsystem, "pci")) {
193 /* The other is connected to the platform bus
194 * and we are a PCI device, that also means we
195 * are out. */
196 log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
197 udev_device_get_sysname(device),
198 udev_device_get_sysname(other));
199 return false;
200 }
201 }
202
203 return true;
204 }
205
206 static unsigned get_max_brightness(struct udev_device *device) {
207 int r;
208 const char *max_brightness_str;
209 unsigned max_brightness;
210
211 max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness");
212 if (!max_brightness_str) {
213 log_warning("Failed to read 'max_brightness' attribute.");
214 return 0;
215 }
216
217 r = safe_atou(max_brightness_str, &max_brightness);
218 if (r < 0) {
219 log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str);
220 return 0;
221 }
222
223 if (max_brightness <= 0) {
224 log_warning("Maximum brightness is 0, ignoring device.");
225 return 0;
226 }
227
228 return max_brightness;
229 }
230
231 /* Some systems turn the backlight all the way off at the lowest levels.
232 * clamp_brightness clamps the saved brightness to at least 1 or 5% of
233 * max_brightness in case of 'backlight' subsystem. This avoids preserving
234 * an unreadably dim screen, which would otherwise force the user to
235 * disable state restoration. */
236 static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) {
237 int r;
238 unsigned brightness, new_brightness, min_brightness;
239 const char *subsystem;
240
241 r = safe_atou(*value, &brightness);
242 if (r < 0) {
243 log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value);
244 return;
245 }
246
247 subsystem = udev_device_get_subsystem(device);
248 if (streq_ptr(subsystem, "backlight"))
249 min_brightness = MAX(1U, max_brightness/20);
250 else
251 min_brightness = 0;
252
253 new_brightness = CLAMP(brightness, min_brightness, max_brightness);
254 if (new_brightness != brightness) {
255 char *old_value = *value;
256
257 r = asprintf(value, "%u", new_brightness);
258 if (r < 0) {
259 log_oom();
260 return;
261 }
262
263 log_info("Saved brightness %s %s to %s.", old_value,
264 new_brightness > brightness ?
265 "too low; increasing" : "too high; decreasing",
266 *value);
267
268 free(old_value);
269 }
270 }
271
272 int main(int argc, char *argv[]) {
273 _cleanup_udev_unref_ struct udev *udev = NULL;
274 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
275 _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
276 const char *sysname, *path_id;
277 unsigned max_brightness;
278 int r;
279
280 if (argc != 3) {
281 log_error("This program requires two arguments.");
282 return EXIT_FAILURE;
283 }
284
285 log_set_target(LOG_TARGET_AUTO);
286 log_parse_environment();
287 log_open();
288
289 umask(0022);
290
291 r = mkdir_p("/var/lib/systemd/backlight", 0755);
292 if (r < 0) {
293 log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
294 return EXIT_FAILURE;
295 }
296
297 udev = udev_new();
298 if (!udev) {
299 log_oom();
300 return EXIT_FAILURE;
301 }
302
303 sysname = strchr(argv[2], ':');
304 if (!sysname) {
305 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
306 return EXIT_FAILURE;
307 }
308
309 ss = strndup(argv[2], sysname - argv[2]);
310 if (!ss) {
311 log_oom();
312 return EXIT_FAILURE;
313 }
314
315 sysname++;
316
317 if (!streq(ss, "backlight") && !streq(ss, "leds")) {
318 log_error("Not a backlight or LED device: '%s:%s'", ss, sysname);
319 return EXIT_FAILURE;
320 }
321
322 errno = 0;
323 device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
324 if (!device) {
325 if (errno > 0)
326 log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
327 else
328 log_oom();
329
330 return EXIT_FAILURE;
331 }
332
333 /* If max_brightness is 0, then there is no actual backlight
334 * device. This happens on desktops with Asus mainboards
335 * that load the eeepc-wmi module.
336 */
337 max_brightness = get_max_brightness(device);
338 if (max_brightness == 0)
339 return EXIT_SUCCESS;
340
341 escaped_ss = cescape(ss);
342 if (!escaped_ss) {
343 log_oom();
344 return EXIT_FAILURE;
345 }
346
347 escaped_sysname = cescape(sysname);
348 if (!escaped_sysname) {
349 log_oom();
350 return EXIT_FAILURE;
351 }
352
353 path_id = udev_device_get_property_value(device, "ID_PATH");
354 if (path_id) {
355 escaped_path_id = cescape(path_id);
356 if (!escaped_path_id) {
357 log_oom();
358 return EXIT_FAILURE;
359 }
360
361 saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname);
362 } else
363 saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname);
364
365 if (!saved) {
366 log_oom();
367 return EXIT_FAILURE;
368 }
369
370 /* If there are multiple conflicting backlight devices, then
371 * their probing at boot-time might happen in any order. This
372 * means the validity checking of the device then is not
373 * reliable, since it might not see other devices conflicting
374 * with a specific backlight. To deal with this, we will
375 * actively delete backlight state files at shutdown (where
376 * device probing should be complete), so that the validity
377 * check at boot time doesn't have to be reliable. */
378
379 if (streq(argv[1], "load")) {
380 _cleanup_free_ char *value = NULL;
381 const char *clamp;
382
383 if (shall_restore_state() == 0)
384 return EXIT_SUCCESS;
385
386 if (!validate_device(udev, device))
387 return EXIT_SUCCESS;
388
389 r = read_one_line_file(saved, &value);
390 if (r < 0) {
391
392 if (r == -ENOENT)
393 return EXIT_SUCCESS;
394
395 log_error_errno(r, "Failed to read %s: %m", saved);
396 return EXIT_FAILURE;
397 }
398
399 clamp = udev_device_get_property_value(device, "ID_BACKLIGHT_CLAMP");
400 if (!clamp || parse_boolean(clamp) != 0) /* default to clamping */
401 clamp_brightness(device, &value, max_brightness);
402
403 r = udev_device_set_sysattr_value(device, "brightness", value);
404 if (r < 0) {
405 log_error_errno(r, "Failed to write system 'brightness' attribute: %m");
406 return EXIT_FAILURE;
407 }
408
409 } else if (streq(argv[1], "save")) {
410 const char *value;
411
412 if (!validate_device(udev, device)) {
413 unlink(saved);
414 return EXIT_SUCCESS;
415 }
416
417 value = udev_device_get_sysattr_value(device, "brightness");
418 if (!value) {
419 log_error("Failed to read system 'brightness' attribute");
420 return EXIT_FAILURE;
421 }
422
423 r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE);
424 if (r < 0) {
425 log_error_errno(r, "Failed to write %s: %m", saved);
426 return EXIT_FAILURE;
427 }
428
429 } else {
430 log_error("Unknown verb %s.", argv[1]);
431 return EXIT_FAILURE;
432 }
433
434 return EXIT_SUCCESS;
435 }