1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include <errno.h>
4 #include <getopt.h>
5 
6 #include "sd-device.h"
7 #include "sd-event.h"
8 
9 #include "alloc-util.h"
10 #include "device-monitor-private.h"
11 #include "device-private.h"
12 #include "device-util.h"
13 #include "fd-util.h"
14 #include "format-util.h"
15 #include "hashmap.h"
16 #include "set.h"
17 #include "signal-util.h"
18 #include "string-util.h"
19 #include "udevadm.h"
20 #include "virt.h"
21 #include "time-util.h"
22 
23 static bool arg_show_property = false;
24 static bool arg_print_kernel = false;
25 static bool arg_print_udev = false;
26 static Set *arg_tag_filter = NULL;
27 static Hashmap *arg_subsystem_filter = NULL;
28 
device_monitor_handler(sd_device_monitor * monitor,sd_device * device,void * userdata)29 static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) {
30         sd_device_action_t action = _SD_DEVICE_ACTION_INVALID;
31         const char *devpath = NULL, *subsystem = NULL;
32         MonitorNetlinkGroup group = PTR_TO_INT(userdata);
33         struct timespec ts;
34 
35         assert(device);
36         assert(IN_SET(group, MONITOR_GROUP_UDEV, MONITOR_GROUP_KERNEL));
37 
38         (void) sd_device_get_action(device, &action);
39         (void) sd_device_get_devpath(device, &devpath);
40         (void) sd_device_get_subsystem(device, &subsystem);
41 
42         assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
43 
44         printf("%-6s[%"PRI_TIME".%06"PRI_NSEC"] %-8s %s (%s)\n",
45                group == MONITOR_GROUP_UDEV ? "UDEV" : "KERNEL",
46                ts.tv_sec, (nsec_t)ts.tv_nsec/1000,
47                strna(device_action_to_string(action)),
48                devpath, subsystem);
49 
50         if (arg_show_property) {
51                 const char *key, *value;
52 
53                 FOREACH_DEVICE_PROPERTY(device, key, value)
54                         printf("%s=%s\n", key, value);
55 
56                 printf("\n");
57         }
58 
59         return 0;
60 }
61 
setup_monitor(MonitorNetlinkGroup sender,sd_event * event,sd_device_monitor ** ret)62 static int setup_monitor(MonitorNetlinkGroup sender, sd_event *event, sd_device_monitor **ret) {
63         _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
64         const char *subsystem, *devtype, *tag;
65         int r;
66 
67         r = device_monitor_new_full(&monitor, sender, -1);
68         if (r < 0)
69                 return log_error_errno(r, "Failed to create netlink socket: %m");
70 
71         (void) sd_device_monitor_set_receive_buffer_size(monitor, 128*1024*1024);
72 
73         r = sd_device_monitor_attach_event(monitor, event);
74         if (r < 0)
75                 return log_error_errno(r, "Failed to attach event: %m");
76 
77         HASHMAP_FOREACH_KEY(devtype, subsystem, arg_subsystem_filter) {
78                 r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, subsystem, devtype);
79                 if (r < 0)
80                         return log_error_errno(r, "Failed to apply subsystem filter '%s%s%s': %m",
81                                                subsystem, devtype ? "/" : "", strempty(devtype));
82         }
83 
84         SET_FOREACH(tag, arg_tag_filter) {
85                 r = sd_device_monitor_filter_add_match_tag(monitor, tag);
86                 if (r < 0)
87                         return log_error_errno(r, "Failed to apply tag filter '%s': %m", tag);
88         }
89 
90         r = sd_device_monitor_start(monitor, device_monitor_handler, INT_TO_PTR(sender));
91         if (r < 0)
92                 return log_error_errno(r, "Failed to start device monitor: %m");
93 
94         (void) sd_event_source_set_description(sd_device_monitor_get_event_source(monitor),
95                                                sender == MONITOR_GROUP_UDEV ? "device-monitor-udev" : "device-monitor-kernel");
96 
97         *ret = TAKE_PTR(monitor);
98         return 0;
99 }
100 
help(void)101 static int help(void) {
102         printf("%s monitor [OPTIONS]\n\n"
103                "Listen to kernel and udev events.\n\n"
104                "  -h --help                                Show this help\n"
105                "  -V --version                             Show package version\n"
106                "  -p --property                            Print the event properties\n"
107                "  -k --kernel                              Print kernel uevents\n"
108                "  -u --udev                                Print udev events\n"
109                "  -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem\n"
110                "  -t --tag-match=TAG                       Filter events by tag\n",
111                program_invocation_short_name);
112 
113         return 0;
114 }
115 
parse_argv(int argc,char * argv[])116 static int parse_argv(int argc, char *argv[]) {
117         static const struct option options[] = {
118                 { "property",        no_argument,       NULL, 'p' },
119                 { "environment",     no_argument,       NULL, 'e' }, /* alias for -p */
120                 { "kernel",          no_argument,       NULL, 'k' },
121                 { "udev",            no_argument,       NULL, 'u' },
122                 { "subsystem-match", required_argument, NULL, 's' },
123                 { "tag-match",       required_argument, NULL, 't' },
124                 { "version",         no_argument,       NULL, 'V' },
125                 { "help",            no_argument,       NULL, 'h' },
126                 {}
127         };
128 
129         int r, c;
130 
131         while ((c = getopt_long(argc, argv, "pekus:t:Vh", options, NULL)) >= 0)
132                 switch (c) {
133                 case 'p':
134                 case 'e':
135                         arg_show_property = true;
136                         break;
137                 case 'k':
138                         arg_print_kernel = true;
139                         break;
140                 case 'u':
141                         arg_print_udev = true;
142                         break;
143                 case 's': {
144                         _cleanup_free_ char *subsystem = NULL, *devtype = NULL;
145                         const char *slash;
146 
147                         slash = strchr(optarg, '/');
148                         if (slash) {
149                                 devtype = strdup(slash + 1);
150                                 if (!devtype)
151                                         return -ENOMEM;
152 
153                                 subsystem = strndup(optarg, slash - optarg);
154                         } else
155                                 subsystem = strdup(optarg);
156 
157                         if (!subsystem)
158                                 return -ENOMEM;
159 
160                         r = hashmap_ensure_put(&arg_subsystem_filter, NULL, subsystem, devtype);
161                         if (r < 0)
162                                 return r;
163 
164                         TAKE_PTR(subsystem);
165                         TAKE_PTR(devtype);
166                         break;
167                 }
168                 case 't':
169                         /* optarg is stored in argv[], so we don't need to copy it */
170                         r = set_ensure_put(&arg_tag_filter, &string_hash_ops, optarg);
171                         if (r < 0)
172                                 return r;
173                         break;
174 
175                 case 'V':
176                         return print_version();
177                 case 'h':
178                         return help();
179                 case '?':
180                         return -EINVAL;
181                 default:
182                         assert_not_reached();
183                 }
184 
185         if (!arg_print_kernel && !arg_print_udev) {
186                 arg_print_kernel = true;
187                 arg_print_udev = true;
188         }
189 
190         return 1;
191 }
192 
monitor_main(int argc,char * argv[],void * userdata)193 int monitor_main(int argc, char *argv[], void *userdata) {
194         _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *kernel_monitor = NULL, *udev_monitor = NULL;
195         _cleanup_(sd_event_unrefp) sd_event *event = NULL;
196         int r;
197 
198         r = parse_argv(argc, argv);
199         if (r <= 0)
200                 goto finalize;
201 
202         if (running_in_chroot() > 0) {
203                 log_info("Running in chroot, ignoring request.");
204                 return 0;
205         }
206 
207         /* Callers are expecting to see events as they happen: Line buffering */
208         setlinebuf(stdout);
209 
210         r = sd_event_default(&event);
211         if (r < 0) {
212                 log_error_errno(r, "Failed to initialize event: %m");
213                 goto finalize;
214         }
215 
216         assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
217         (void) sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
218         (void) sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
219 
220         printf("monitor will print the received events for:\n");
221         if (arg_print_udev) {
222                 r = setup_monitor(MONITOR_GROUP_UDEV, event, &udev_monitor);
223                 if (r < 0)
224                         goto finalize;
225 
226                 printf("UDEV - the event which udev sends out after rule processing\n");
227         }
228 
229         if (arg_print_kernel) {
230                 r = setup_monitor(MONITOR_GROUP_KERNEL, event, &kernel_monitor);
231                 if (r < 0)
232                         goto finalize;
233 
234                 printf("KERNEL - the kernel uevent\n");
235         }
236         printf("\n");
237 
238         r = sd_event_loop(event);
239         if (r < 0) {
240                 log_error_errno(r, "Failed to run event loop: %m");
241                 goto finalize;
242         }
243 
244         r = 0;
245 
246 finalize:
247         hashmap_free_free_free(arg_subsystem_filter);
248         set_free(arg_tag_filter);
249 
250         return r;
251 }
252