udev: fix codesonar warnings
[thirdparty/systemd.git] / src / udev / udevadm-info.c
1 /* SPDX-License-Identifier: GPL-2.0+ */
2
3 #include <ctype.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <getopt.h>
7 #include <stddef.h>
8 #include <stdio.h>
9 #include <sys/stat.h>
10 #include <unistd.h>
11
12 #include "sd-device.h"
13
14 #include "alloc-util.h"
15 #include "device-enumerator-private.h"
16 #include "device-private.h"
17 #include "device-util.h"
18 #include "dirent-util.h"
19 #include "fd-util.h"
20 #include "sort-util.h"
21 #include "string-table.h"
22 #include "string-util.h"
23 #include "udev-util.h"
24 #include "udevadm-util.h"
25 #include "udevadm.h"
26
27 typedef enum ActionType {
28         ACTION_QUERY,
29         ACTION_ATTRIBUTE_WALK,
30         ACTION_DEVICE_ID_FILE,
31 } ActionType;
32
33 typedef enum QueryType {
34         QUERY_NAME,
35         QUERY_PATH,
36         QUERY_SYMLINK,
37         QUERY_PROPERTY,
38         QUERY_ALL,
39 } QueryType;
40
41 static bool arg_root = false;
42 static bool arg_export = false;
43 static const char *arg_export_prefix = NULL;
44 static usec_t arg_wait_for_initialization_timeout = 0;
45
46 static bool skip_attribute(const char *name) {
47         /* Those are either displayed separately or should not be shown at all. */
48         return STR_IN_SET(name,
49                           "uevent",
50                           "dev",
51                           "modalias",
52                           "resource",
53                           "driver",
54                           "subsystem",
55                           "module");
56 }
57
58 typedef struct SysAttr {
59         const char *name;
60         const char *value;
61 } SysAttr;
62
63 static int sysattr_compare(const SysAttr *a, const SysAttr *b) {
64         return strcmp(a->name, b->name);
65 }
66
67 static int print_all_attributes(sd_device *device, bool is_parent) {
68         _cleanup_free_ SysAttr *sysattrs = NULL;
69         size_t n_items = 0, n_allocated = 0;
70         const char *name, *value;
71
72         value = NULL;
73         (void) sd_device_get_devpath(device, &value);
74         printf("  looking at %sdevice '%s':\n", is_parent ? "parent " : "", strempty(value));
75
76         value = NULL;
77         (void) sd_device_get_sysname(device, &value);
78         printf("    %s==\"%s\"\n", is_parent ? "KERNELS" : "KERNEL", strempty(value));
79
80         value = NULL;
81         (void) sd_device_get_subsystem(device, &value);
82         printf("    %s==\"%s\"\n", is_parent ? "SUBSYSTEMS" : "SUBSYSTEM", strempty(value));
83
84         value = NULL;
85         (void) sd_device_get_driver(device, &value);
86         printf("    %s==\"%s\"\n", is_parent ? "DRIVERS" : "DRIVER", strempty(value));
87
88         FOREACH_DEVICE_SYSATTR(device, name) {
89                 size_t len;
90
91                 if (skip_attribute(name))
92                         continue;
93
94                 if (sd_device_get_sysattr_value(device, name, &value) < 0)
95                         continue;
96
97                 /* skip any values that look like a path */
98                 if (value[0] == '/')
99                         continue;
100
101                 /* skip nonprintable attributes */
102                 len = strlen(value);
103                 while (len > 0 && isprint((unsigned char) value[len-1]))
104                         len--;
105                 if (len > 0)
106                         continue;
107
108                 if (!GREEDY_REALLOC(sysattrs, n_allocated, n_items + 1))
109                         return log_oom();
110
111                 sysattrs[n_items] = (SysAttr) {
112                         .name = name,
113                         .value = value,
114                 };
115                 n_items++;
116         }
117
118         typesafe_qsort(sysattrs, n_items, sysattr_compare);
119
120         for (size_t i = 0; i < n_items; i++)
121                 printf("    %s{%s}==\"%s\"\n", is_parent ? "ATTRS" : "ATTR", sysattrs[i].name, sysattrs[i].value);
122
123         puts("");
124
125         return 0;
126 }
127
128 static int print_device_chain(sd_device *device) {
129         sd_device *child, *parent;
130         int r;
131
132         printf("\n"
133                "Udevadm info starts with the device specified by the devpath and then\n"
134                "walks up the chain of parent devices. It prints for every device\n"
135                "found, all possible attributes in the udev rules key format.\n"
136                "A rule to match, can be composed by the attributes of the device\n"
137                "and the attributes from one single parent device.\n"
138                "\n");
139
140         r = print_all_attributes(device, false);
141         if (r < 0)
142                 return r;
143
144         for (child = device; sd_device_get_parent(child, &parent) >= 0; child = parent) {
145                 r = print_all_attributes(parent, true);
146                 if (r < 0)
147                         return r;
148         }
149
150         return 0;
151 }
152
153 static int print_record(sd_device *device) {
154         const char *str, *val;
155         int i;
156
157         (void) sd_device_get_devpath(device, &str);
158         printf("P: %s\n", str);
159
160         if (sd_device_get_devname(device, &str) >= 0) {
161                 assert_se(val = path_startswith(str, "/dev/"));
162                 printf("N: %s\n", val);
163         }
164
165         if (device_get_devlink_priority(device, &i) >= 0)
166                 printf("L: %i\n", i);
167
168         FOREACH_DEVICE_DEVLINK(device, str) {
169                 assert_se(val = path_startswith(str, "/dev/"));
170                 printf("S: %s\n", val);
171         }
172
173         FOREACH_DEVICE_PROPERTY(device, str, val)
174                 printf("E: %s=%s\n", str, val);
175
176         puts("");
177         return 0;
178 }
179
180 static int stat_device(const char *name, bool export, const char *prefix) {
181         struct stat statbuf;
182
183         if (stat(name, &statbuf) != 0)
184                 return -errno;
185
186         if (export) {
187                 if (!prefix)
188                         prefix = "INFO_";
189                 printf("%sMAJOR=%u\n"
190                        "%sMINOR=%u\n",
191                        prefix, major(statbuf.st_dev),
192                        prefix, minor(statbuf.st_dev));
193         } else
194                 printf("%u:%u\n", major(statbuf.st_dev), minor(statbuf.st_dev));
195         return 0;
196 }
197
198 static int export_devices(void) {
199         _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
200         sd_device *d;
201         int r;
202
203         r = sd_device_enumerator_new(&e);
204         if (r < 0)
205                 return log_oom();
206
207         r = sd_device_enumerator_allow_uninitialized(e);
208         if (r < 0)
209                 return log_error_errno(r, "Failed to set allowing uninitialized flag: %m");
210
211         r = device_enumerator_scan_devices(e);
212         if (r < 0)
213                 return log_error_errno(r, "Failed to scan devices: %m");
214
215         FOREACH_DEVICE_AND_SUBSYSTEM(e, d)
216                 (void) print_record(d);
217
218         return 0;
219 }
220
221 static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
222         struct dirent *dent;
223
224         if (depth <= 0)
225                 return;
226
227         FOREACH_DIRENT_ALL(dent, dir, break) {
228                 struct stat stats;
229
230                 if (dent->d_name[0] == '.')
231                         continue;
232                 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
233                         continue;
234                 if ((stats.st_mode & mask) != 0)
235                         continue;
236                 if (S_ISDIR(stats.st_mode)) {
237                         _cleanup_closedir_ DIR *dir2 = NULL;
238
239                         dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
240                         if (dir2)
241                                 cleanup_dir(dir2, mask, depth-1);
242
243                         (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
244                 } else
245                         (void) unlinkat(dirfd(dir), dent->d_name, 0);
246         }
247 }
248
249 static void cleanup_db(void) {
250         _cleanup_closedir_ DIR *dir1 = NULL, *dir2 = NULL, *dir3 = NULL, *dir4 = NULL, *dir5 = NULL;
251
252         (void) unlink("/run/udev/queue.bin");
253
254         dir1 = opendir("/run/udev/data");
255         if (dir1)
256                 cleanup_dir(dir1, S_ISVTX, 1);
257
258         dir2 = opendir("/run/udev/links");
259         if (dir2)
260                 cleanup_dir(dir2, 0, 2);
261
262         dir3 = opendir("/run/udev/tags");
263         if (dir3)
264                 cleanup_dir(dir3, 0, 2);
265
266         dir4 = opendir("/run/udev/static_node-tags");
267         if (dir4)
268                 cleanup_dir(dir4, 0, 2);
269
270         dir5 = opendir("/run/udev/watch");
271         if (dir5)
272                 cleanup_dir(dir5, 0, 1);
273 }
274
275 static int query_device(QueryType query, sd_device* device) {
276         int r;
277
278         assert(device);
279
280         switch(query) {
281         case QUERY_NAME: {
282                 const char *node;
283
284                 r = sd_device_get_devname(device, &node);
285                 if (r < 0)
286                         return log_error_errno(r, "No device node found: %m");
287
288                 if (!arg_root)
289                         assert_se(node = path_startswith(node, "/dev/"));
290                 printf("%s\n", node);
291                 return 0;
292         }
293
294         case QUERY_SYMLINK: {
295                 const char *devlink, *prefix = "";
296
297                 FOREACH_DEVICE_DEVLINK(device, devlink) {
298                         if (!arg_root)
299                                 assert_se(devlink = path_startswith(devlink, "/dev/"));
300                         printf("%s%s", prefix, devlink);
301                         prefix = " ";
302                 }
303                 puts("");
304                 return 0;
305         }
306
307         case QUERY_PATH: {
308                 const char *devpath;
309
310                 r = sd_device_get_devpath(device, &devpath);
311                 if (r < 0)
312                         return log_error_errno(r, "Failed to get device path: %m");
313
314                 printf("%s\n", devpath);
315                 return 0;
316         }
317
318         case QUERY_PROPERTY: {
319                 const char *key, *value;
320
321                 FOREACH_DEVICE_PROPERTY(device, key, value)
322                         if (arg_export)
323                                 printf("%s%s='%s'\n", strempty(arg_export_prefix), key, value);
324                         else
325                                 printf("%s=%s\n", key, value);
326                 return 0;
327         }
328
329         case QUERY_ALL:
330                 return print_record(device);
331         }
332
333         assert_not_reached("unknown query type");
334         return 0;
335 }
336
337 static int help(void) {
338         printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
339                "Query sysfs or the udev database.\n\n"
340                "  -h --help                   Print this message\n"
341                "  -V --version                Print version of the program\n"
342                "  -q --query=TYPE             Query device information:\n"
343                "       name                     Name of device node\n"
344                "       symlink                  Pointing to node\n"
345                "       path                     sysfs device path\n"
346                "       property                 The device properties\n"
347                "       all                      All values\n"
348                "  -p --path=SYSPATH           sysfs device path used for query or attribute walk\n"
349                "  -n --name=NAME              Node or symlink name used for query or attribute walk\n"
350                "  -r --root                   Prepend dev directory to path names\n"
351                "  -a --attribute-walk         Print all key matches walking along the chain\n"
352                "                              of parent devices\n"
353                "  -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
354                "  -x --export                 Export key/value pairs\n"
355                "  -P --export-prefix          Export the key name with a prefix\n"
356                "  -e --export-db              Export the content of the udev database\n"
357                "  -c --cleanup-db             Clean up the udev database\n"
358                "  -w --wait-for-initialization[=SECONDS]\n"
359                "                              Wait for device to be initialized\n"
360                , program_invocation_short_name);
361
362         return 0;
363 }
364
365 int info_main(int argc, char *argv[], void *userdata) {
366         _cleanup_strv_free_ char **devices = NULL;
367         _cleanup_free_ char *name = NULL;
368         int c, r;
369
370         static const struct option options[] = {
371                 { "name",                    required_argument, NULL, 'n' },
372                 { "path",                    required_argument, NULL, 'p' },
373                 { "query",                   required_argument, NULL, 'q' },
374                 { "attribute-walk",          no_argument,       NULL, 'a' },
375                 { "cleanup-db",              no_argument,       NULL, 'c' },
376                 { "export-db",               no_argument,       NULL, 'e' },
377                 { "root",                    no_argument,       NULL, 'r' },
378                 { "device-id-of-file",       required_argument, NULL, 'd' },
379                 { "export",                  no_argument,       NULL, 'x' },
380                 { "export-prefix",           required_argument, NULL, 'P' },
381                 { "wait-for-initialization", optional_argument, NULL, 'w' },
382                 { "version",                 no_argument,       NULL, 'V' },
383                 { "help",                    no_argument,       NULL, 'h' },
384                 {}
385         };
386
387         ActionType action = ACTION_QUERY;
388         QueryType query = QUERY_ALL;
389
390         while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:w::Vh", options, NULL)) >= 0)
391                 switch (c) {
392                 case 'n':
393                 case 'p': {
394                         const char *prefix = c == 'n' ? "/dev/" : "/sys/";
395                         char *path;
396
397                         path = path_join(path_startswith(optarg, prefix) ? NULL : prefix, optarg);
398                         if (!path)
399                                 return log_oom();
400
401                         r = strv_consume(&devices, path);
402                         if (r < 0)
403                                 return log_oom();
404                         break;
405                 }
406
407                 case 'q':
408                         action = ACTION_QUERY;
409                         if (streq(optarg, "property") || streq(optarg, "env"))
410                                 query = QUERY_PROPERTY;
411                         else if (streq(optarg, "name"))
412                                 query = QUERY_NAME;
413                         else if (streq(optarg, "symlink"))
414                                 query = QUERY_SYMLINK;
415                         else if (streq(optarg, "path"))
416                                 query = QUERY_PATH;
417                         else if (streq(optarg, "all"))
418                                 query = QUERY_ALL;
419                         else
420                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "unknown query type");
421                         break;
422                 case 'r':
423                         arg_root = true;
424                         break;
425                 case 'd':
426                         action = ACTION_DEVICE_ID_FILE;
427                         r = free_and_strdup(&name, optarg);
428                         if (r < 0)
429                                 return log_oom();
430                         break;
431                 case 'a':
432                         action = ACTION_ATTRIBUTE_WALK;
433                         break;
434                 case 'e':
435                         return export_devices();
436                 case 'c':
437                         cleanup_db();
438                         return 0;
439                 case 'x':
440                         arg_export = true;
441                         break;
442                 case 'P':
443                         arg_export = true;
444                         arg_export_prefix = optarg;
445                         break;
446                 case 'w':
447                         if (optarg) {
448                                 r = parse_sec(optarg, &arg_wait_for_initialization_timeout);
449                                 if (r < 0)
450                                         return log_error_errno(r, "Failed to parse timeout value: %m");
451                         } else
452                                 arg_wait_for_initialization_timeout = USEC_INFINITY;
453                         break;
454                 case 'V':
455                         return print_version();
456                 case 'h':
457                         return help();
458                 case '?':
459                         return -EINVAL;
460                 default:
461                         assert_not_reached("Unknown option");
462                 }
463
464         if (action == ACTION_DEVICE_ID_FILE) {
465                 if (argv[optind])
466                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
467                                                "Positional arguments are not allowed with -d/--device-id-of-file.");
468                 assert(name);
469                 return stat_device(name, arg_export, arg_export_prefix);
470         }
471
472         r = strv_extend_strv(&devices, argv + optind, false);
473         if (r < 0)
474                 return log_error_errno(r, "Failed to build argument list: %m");
475
476         if (strv_isempty(devices))
477                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
478                                        "A device name or path is required");
479         if (action == ACTION_ATTRIBUTE_WALK && strv_length(devices) > 1)
480                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
481                                        "Only one device may be specified with -a/--attribute-walk");
482
483         char **p;
484         STRV_FOREACH(p, devices) {
485                 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
486
487                 r = find_device(*p, NULL, &device);
488                 if (r == -EINVAL)
489                         return log_error_errno(r, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys or a unit name: %m", *p);
490                 if (r < 0)
491                         return log_error_errno(r, "Unknown device \"%s\": %m",  *p);
492
493                 if (arg_wait_for_initialization_timeout > 0) {
494                         sd_device *d;
495
496                         r = device_wait_for_initialization(device, NULL, arg_wait_for_initialization_timeout, &d);
497                         if (r < 0)
498                                 return r;
499
500                         sd_device_unref(device);
501                         device = d;
502                 }
503
504                 if (action == ACTION_QUERY)
505                         r = query_device(query, device);
506                 else if (action == ACTION_ATTRIBUTE_WALK)
507                         r = print_device_chain(device);
508                 else
509                         assert_not_reached("Unknown action");
510                 if (r < 0)
511                         return r;
512         }
513
514         return 0;
515 }