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