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