]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/backlight/backlight.c
util: unify reading of /proc/cmdline
[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 "util.h"
23 #include "mkdir.h"
24 #include "fileio.h"
25 #include "libudev.h"
26 #include "udev-util.h"
27 #include "util.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, "0123456789");
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;
69
70 if (safe_atolu(value, &class) < 0) {
71 log_warning("Cannot parse PCI class %s of device %s:%s.", value, subsystem, sysname);
72 return NULL;
73 }
74
75 /* Graphics card */
76 if (class == 0x30000)
77 return parent;
78 }
79
80 } else if (streq(subsystem, "platform"))
81 return parent;
82
83 return find_pci_or_platform_parent(parent);
84 }
85
86 static bool same_device(struct udev_device *a, struct udev_device *b) {
87 assert(a);
88 assert(b);
89
90 if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b)))
91 return false;
92
93 if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b)))
94 return false;
95
96 return true;
97 }
98
99 static bool validate_device(struct udev *udev, struct udev_device *device) {
100 _cleanup_udev_enumerate_unref_ struct udev_enumerate *enumerate = NULL;
101 struct udev_list_entry *item = NULL, *first = NULL;
102 struct udev_device *parent;
103 const char *v, *subsystem;
104 int r;
105
106 assert(udev);
107 assert(device);
108
109 /* Verify whether we should actually care for a specific
110 * backlight device. For backlight devices there might be
111 * multiple ways to access the same control: "firmware"
112 * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
113 * "raw" (via the graphics card). In general we should prefer
114 * "firmware" (i.e. ACPI) or "platform" access over "raw"
115 * access, in order not to confuse the BIOS/EC, and
116 * compatibility with possible low-level hotkey handling of
117 * screen brightness. The kernel will already make sure to
118 * expose only one of "firmware" and "platform" for the same
119 * device to userspace. However, we still need to make sure
120 * that we use "raw" only if no "firmware" or "platform"
121 * device for the same device exists. */
122
123 subsystem = udev_device_get_subsystem(device);
124 if (!streq_ptr(subsystem, "backlight"))
125 return true;
126
127 v = udev_device_get_sysattr_value(device, "type");
128 if (!streq_ptr(v, "raw"))
129 return true;
130
131 parent = find_pci_or_platform_parent(device);
132 if (!parent)
133 return true;
134
135 subsystem = udev_device_get_subsystem(parent);
136 if (!subsystem)
137 return true;
138
139 enumerate = udev_enumerate_new(udev);
140 if (!enumerate)
141 return true;
142
143 r = udev_enumerate_add_match_subsystem(enumerate, "backlight");
144 if (r < 0)
145 return true;
146
147 r = udev_enumerate_scan_devices(enumerate);
148 if (r < 0)
149 return true;
150
151 first = udev_enumerate_get_list_entry(enumerate);
152 udev_list_entry_foreach(item, first) {
153 _cleanup_udev_device_unref_ struct udev_device *other;
154 struct udev_device *other_parent;
155 const char *other_subsystem;
156
157 other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
158 if (!other)
159 return true;
160
161 if (same_device(device, other))
162 continue;
163
164 v = udev_device_get_sysattr_value(other, "type");
165 if (!streq_ptr(v, "platform") && !streq_ptr(v, "firmware"))
166 continue;
167
168 /* OK, so there's another backlight device, and it's a
169 * platform or firmware device, so, let's see if we
170 * can verify it belongs to the same device as
171 * ours. */
172 other_parent = find_pci_or_platform_parent(other);
173 if (!other_parent)
174 continue;
175
176 if (same_device(parent, other_parent)) {
177 /* Both have the same PCI parent, that means
178 * we are out. */
179 log_debug("Skipping backlight device %s, since backlight device %s is on same PCI device and, takes precedence.", udev_device_get_sysname(device), udev_device_get_sysname(other));
180 return false;
181 }
182
183 other_subsystem = udev_device_get_subsystem(other_parent);
184 if (streq_ptr(other_subsystem, "platform") && streq_ptr(subsystem, "pci")) {
185 /* The other is connected to the platform bus
186 * and we are a PCI device, that also means we
187 * are out. */
188 log_debug("Skipping backlight device %s, since backlight device %s is a platform device and takes precedence.", udev_device_get_sysname(device), udev_device_get_sysname(other));
189 return false;
190 }
191 }
192
193 return true;
194 }
195
196 int main(int argc, char *argv[]) {
197 _cleanup_udev_unref_ struct udev *udev = NULL;
198 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
199 _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
200 const char *sysname, *path_id;
201 int r;
202
203 if (argc != 3) {
204 log_error("This program requires two arguments.");
205 return EXIT_FAILURE;
206 }
207
208 log_set_target(LOG_TARGET_AUTO);
209 log_parse_environment();
210 log_open();
211
212 umask(0022);
213
214 r = mkdir_p("/var/lib/systemd/backlight", 0755);
215 if (r < 0) {
216 log_error("Failed to create backlight directory: %s", strerror(-r));
217 return EXIT_FAILURE;
218 }
219
220 udev = udev_new();
221 if (!udev) {
222 log_oom();
223 return EXIT_FAILURE;
224 }
225
226 sysname = strchr(argv[2], ':');
227 if (!sysname) {
228 log_error("Requires pair of subsystem and sysname for specifying backlight device.");
229 return EXIT_FAILURE;
230 }
231
232 ss = strndup(argv[2], sysname - argv[2]);
233 if (!ss) {
234 log_oom();
235 return EXIT_FAILURE;
236 }
237
238 sysname++;
239
240 if (!streq(ss, "backlight") && !streq(ss, "leds")) {
241 log_error("Not a backlight or LED device: '%s:%s'", ss, sysname);
242 return EXIT_FAILURE;
243 }
244
245 errno = 0;
246 device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
247 if (!device) {
248 if (errno != 0)
249 log_error("Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
250 else
251 log_oom();
252
253 return EXIT_FAILURE;
254 }
255
256 escaped_ss = cescape(ss);
257 if (!escaped_ss) {
258 log_oom();
259 return EXIT_FAILURE;
260 }
261
262 escaped_sysname = cescape(sysname);
263 if (!escaped_sysname) {
264 log_oom();
265 return EXIT_FAILURE;
266 }
267
268 path_id = udev_device_get_property_value(device, "ID_PATH");
269 if (path_id) {
270 escaped_path_id = cescape(path_id);
271 if (!escaped_path_id) {
272 log_oom();
273 return EXIT_FAILURE;
274 }
275
276 saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL);
277 } else
278 saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL);
279
280 if (!saved) {
281 log_oom();
282 return EXIT_FAILURE;
283 }
284
285 /* If there are multiple conflicting backlight devices, then
286 * their probing at boot-time might happen in any order. This
287 * means the validity checking of the device then is not
288 * reliable, since it might not see other devices conflicting
289 * with a specific backlight. To deal with this we will
290 * actively delete backlight state files at shutdown (where
291 * device probing should be complete), so that the validity
292 * check at boot time doesn't have to be reliable. */
293
294 if (streq(argv[1], "load") && shall_restore_state()) {
295 _cleanup_free_ char *value = NULL;
296
297 if (!validate_device(udev, device))
298 return EXIT_SUCCESS;
299
300 r = read_one_line_file(saved, &value);
301 if (r < 0) {
302
303 if (r == -ENOENT)
304 return EXIT_SUCCESS;
305
306 log_error("Failed to read %s: %s", saved, strerror(-r));
307 return EXIT_FAILURE;
308 }
309
310 r = udev_device_set_sysattr_value(device, "brightness", value);
311 if (r < 0) {
312 log_error("Failed to write system attribute: %s", strerror(-r));
313 return EXIT_FAILURE;
314 }
315
316 } else if (streq(argv[1], "save")) {
317 const char *value;
318
319 if (!validate_device(udev, device)) {
320 unlink(saved);
321 return EXIT_SUCCESS;
322 }
323
324 value = udev_device_get_sysattr_value(device, "brightness");
325 if (!value) {
326 log_error("Failed to read system attribute: %s", strerror(-r));
327 return EXIT_FAILURE;
328 }
329
330 r = write_string_file(saved, value);
331 if (r < 0) {
332 log_error("Failed to write %s: %s", saved, strerror(-r));
333 return EXIT_FAILURE;
334 }
335
336 } else {
337 log_error("Unknown verb %s.", argv[1]);
338 return EXIT_FAILURE;
339 }
340
341 return EXIT_SUCCESS;
342 }