chiark / gitweb /
sd-rtnl: fix bogus warning about dropping 20 bytes from multi-part 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_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str);
215                 return 0;
216         }
217
218         if (max_brightness <= 0) {
219                 log_warning("Maximum brightness is 0, ignoring device.");
220                 return 0;
221         }
222
223         return max_brightness;
224 }
225
226 /* Some systems turn the backlight all the way off at the lowest levels.
227  * clamp_brightness clamps the saved brightness to at least 1 or 5% of
228  * max_brightness in case of 'backlight' subsystem. This avoids preserving
229  * an unreadably dim screen, which would otherwise force the user to
230  * disable state restoration. */
231 static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) {
232         int r;
233         unsigned brightness, new_brightness, min_brightness;
234         const char *subsystem;
235
236         r = safe_atou(*value, &brightness);
237         if (r < 0) {
238                 log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value);
239                 return;
240         }
241
242         subsystem = udev_device_get_subsystem(device);
243         if (streq_ptr(subsystem, "backlight"))
244                 min_brightness = MAX(1U, max_brightness/20);
245         else
246                 min_brightness = 0;
247
248         new_brightness = CLAMP(brightness, min_brightness, max_brightness);
249         if (new_brightness != brightness) {
250                 char *old_value = *value;
251
252                 r = asprintf(value, "%u", new_brightness);
253                 if (r < 0) {
254                         log_oom();
255                         return;
256                 }
257
258                 log_info("Saved brightness %s %s to %s.", old_value,
259                          new_brightness > brightness ?
260                          "too low; increasing" : "too high; decreasing",
261                          *value);
262
263                 free(old_value);
264         }
265 }
266
267 int main(int argc, char *argv[]) {
268         _cleanup_udev_unref_ struct udev *udev = NULL;
269         _cleanup_udev_device_unref_ struct udev_device *device = NULL;
270         _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
271         const char *sysname, *path_id;
272         unsigned max_brightness;
273         int r;
274
275         if (argc != 3) {
276                 log_error("This program requires two arguments.");
277                 return EXIT_FAILURE;
278         }
279
280         log_set_target(LOG_TARGET_AUTO);
281         log_parse_environment();
282         log_open();
283
284         umask(0022);
285
286         r = mkdir_p("/var/lib/systemd/backlight", 0755);
287         if (r < 0) {
288                 log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
289                 return EXIT_FAILURE;
290         }
291
292         udev = udev_new();
293         if (!udev) {
294                 log_oom();
295                 return EXIT_FAILURE;
296         }
297
298         sysname = strchr(argv[2], ':');
299         if (!sysname) {
300                 log_error("Requires a subsystem and sysname pair specifying a backlight device.");
301                 return EXIT_FAILURE;
302         }
303
304         ss = strndup(argv[2], sysname - argv[2]);
305         if (!ss) {
306                 log_oom();
307                 return EXIT_FAILURE;
308         }
309
310         sysname++;
311
312         if (!streq(ss, "backlight") && !streq(ss, "leds")) {
313                 log_error("Not a backlight or LED device: '%s:%s'", ss, sysname);
314                 return EXIT_FAILURE;
315         }
316
317         errno = 0;
318         device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
319         if (!device) {
320                 if (errno != 0)
321                         log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
322                 else
323                         log_oom();
324
325                 return EXIT_FAILURE;
326         }
327
328         /* If max_brightness is 0, then there is no actual backlight
329          * device. This happens on desktops with Asus mainboards
330          * that load the eeepc-wmi module.
331          */
332         max_brightness = get_max_brightness(device);
333         if (max_brightness == 0)
334                 return EXIT_SUCCESS;
335
336         escaped_ss = cescape(ss);
337         if (!escaped_ss) {
338                 log_oom();
339                 return EXIT_FAILURE;
340         }
341
342         escaped_sysname = cescape(sysname);
343         if (!escaped_sysname) {
344                 log_oom();
345                 return EXIT_FAILURE;
346         }
347
348         path_id = udev_device_get_property_value(device, "ID_PATH");
349         if (path_id) {
350                 escaped_path_id = cescape(path_id);
351                 if (!escaped_path_id) {
352                         log_oom();
353                         return EXIT_FAILURE;
354                 }
355
356                 saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL);
357         } else
358                 saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL);
359
360         if (!saved) {
361                 log_oom();
362                 return EXIT_FAILURE;
363         }
364
365         /* If there are multiple conflicting backlight devices, then
366          * their probing at boot-time might happen in any order. This
367          * means the validity checking of the device then is not
368          * reliable, since it might not see other devices conflicting
369          * with a specific backlight. To deal with this, we will
370          * actively delete backlight state files at shutdown (where
371          * device probing should be complete), so that the validity
372          * check at boot time doesn't have to be reliable. */
373
374         if (streq(argv[1], "load")) {
375                 _cleanup_free_ char *value = NULL;
376
377                 if (!shall_restore_state())
378                         return EXIT_SUCCESS;
379
380                 if (!validate_device(udev, device))
381                         return EXIT_SUCCESS;
382
383                 r = read_one_line_file(saved, &value);
384                 if (r < 0) {
385
386                         if (r == -ENOENT)
387                                 return EXIT_SUCCESS;
388
389                         log_error_errno(r, "Failed to read %s: %m", saved);
390                         return EXIT_FAILURE;
391                 }
392
393                 clamp_brightness(device, &value, max_brightness);
394
395                 r = udev_device_set_sysattr_value(device, "brightness", value);
396                 if (r < 0) {
397                         log_error_errno(r, "Failed to write system 'brightness' attribute: %m");
398                         return EXIT_FAILURE;
399                 }
400
401         } else if (streq(argv[1], "save")) {
402                 const char *value;
403
404                 if (!validate_device(udev, device)) {
405                         unlink(saved);
406                         return EXIT_SUCCESS;
407                 }
408
409                 value = udev_device_get_sysattr_value(device, "brightness");
410                 if (!value) {
411                         log_error("Failed to read system 'brightness' attribute");
412                         return EXIT_FAILURE;
413                 }
414
415                 r = write_string_file(saved, value);
416                 if (r < 0) {
417                         log_error_errno(r, "Failed to write %s: %m", saved);
418                         return EXIT_FAILURE;
419                 }
420
421         } else {
422                 log_error("Unknown verb %s.", argv[1]);
423                 return EXIT_FAILURE;
424         }
425
426         return EXIT_SUCCESS;
427 }