| /* SPDX-License-Identifier: GPL-2.0+ */ |
| |
| #include <errno.h> |
| #include <getopt.h> |
| #include <signal.h> |
| |
| #include "sd-device.h" |
| #include "sd-event.h" |
| |
| #include "alloc-util.h" |
| #include "device-monitor-private.h" |
| #include "device-util.h" |
| #include "fd-util.h" |
| #include "format-util.h" |
| #include "hashmap.h" |
| #include "set.h" |
| #include "signal-util.h" |
| #include "string-util.h" |
| #include "udevadm.h" |
| #include "virt.h" |
| |
| static bool arg_show_property = false; |
| static bool arg_print_kernel = false; |
| static bool arg_print_udev = false; |
| static Set *arg_tag_filter = NULL; |
| static Hashmap *arg_subsystem_filter = NULL; |
| |
| static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) { |
| const char *action = NULL, *devpath = NULL, *subsystem = NULL; |
| MonitorNetlinkGroup group = PTR_TO_INT(userdata); |
| struct timespec ts; |
| |
| assert(device); |
| assert(IN_SET(group, MONITOR_GROUP_UDEV, MONITOR_GROUP_KERNEL)); |
| |
| (void) sd_device_get_property_value(device, "ACTION", &action); |
| (void) sd_device_get_devpath(device, &devpath); |
| (void) sd_device_get_subsystem(device, &subsystem); |
| |
| assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); |
| |
| printf("%-6s[%"PRI_TIME".%06"PRI_NSEC"] %-8s %s (%s)\n", |
| group == MONITOR_GROUP_UDEV ? "UDEV" : "KERNEL", |
| ts.tv_sec, (nsec_t)ts.tv_nsec/1000, |
| action, devpath, subsystem); |
| |
| if (arg_show_property) { |
| const char *key, *value; |
| |
| FOREACH_DEVICE_PROPERTY(device, key, value) |
| printf("%s=%s\n", key, value); |
| |
| printf("\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int setup_monitor(MonitorNetlinkGroup sender, sd_event *event, sd_device_monitor **ret) { |
| _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; |
| const char *subsystem, *devtype, *tag; |
| Iterator i; |
| int r; |
| |
| r = device_monitor_new_full(&monitor, sender, -1); |
| if (r < 0) |
| return log_error_errno(r, "Failed to create netlink socket: %m"); |
| |
| (void) sd_device_monitor_set_receive_buffer_size(monitor, 128*1024*1024); |
| |
| r = sd_device_monitor_attach_event(monitor, event); |
| if (r < 0) |
| return log_error_errno(r, "Failed to attach event: %m"); |
| |
| HASHMAP_FOREACH_KEY(devtype, subsystem, arg_subsystem_filter, i) { |
| r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, subsystem, devtype); |
| if (r < 0) |
| return log_error_errno(r, "Failed to apply subsystem filter '%s%s%s': %m", |
| subsystem, devtype ? "/" : "", strempty(devtype)); |
| } |
| |
| SET_FOREACH(tag, arg_tag_filter, i) { |
| r = sd_device_monitor_filter_add_match_tag(monitor, tag); |
| if (r < 0) |
| return log_error_errno(r, "Failed to apply tag filter '%s': %m", tag); |
| } |
| |
| r = sd_device_monitor_start(monitor, device_monitor_handler, INT_TO_PTR(sender)); |
| if (r < 0) |
| return log_error_errno(r, "Failed to start device monitor: %m"); |
| |
| (void) sd_event_source_set_description(sd_device_monitor_get_event_source(monitor), |
| sender == MONITOR_GROUP_UDEV ? "device-monitor-udev" : "device-monitor-kernel"); |
| |
| *ret = TAKE_PTR(monitor); |
| return 0; |
| } |
| |
| static int help(void) { |
| printf("%s monitor [OPTIONS]\n\n" |
| "Listen to kernel and udev events.\n\n" |
| " -h --help Show this help\n" |
| " -V --version Show package version\n" |
| " -p --property Print the event properties\n" |
| " -k --kernel Print kernel uevents\n" |
| " -u --udev Print udev events\n" |
| " -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem\n" |
| " -t --tag-match=TAG Filter events by tag\n" |
| , program_invocation_short_name); |
| |
| return 0; |
| } |
| |
| static int parse_argv(int argc, char *argv[]) { |
| static const struct option options[] = { |
| { "property", no_argument, NULL, 'p' }, |
| { "environment", no_argument, NULL, 'e' }, /* alias for -p */ |
| { "kernel", no_argument, NULL, 'k' }, |
| { "udev", no_argument, NULL, 'u' }, |
| { "subsystem-match", required_argument, NULL, 's' }, |
| { "tag-match", required_argument, NULL, 't' }, |
| { "version", no_argument, NULL, 'V' }, |
| { "help", no_argument, NULL, 'h' }, |
| {} |
| }; |
| |
| int r, c; |
| |
| while ((c = getopt_long(argc, argv, "pekus:t:Vh", options, NULL)) >= 0) |
| switch (c) { |
| case 'p': |
| case 'e': |
| arg_show_property = true; |
| break; |
| case 'k': |
| arg_print_kernel = true; |
| break; |
| case 'u': |
| arg_print_udev = true; |
| break; |
| case 's': { |
| _cleanup_free_ char *subsystem = NULL, *devtype = NULL; |
| const char *slash; |
| |
| slash = strchr(optarg, '/'); |
| if (slash) { |
| devtype = strdup(slash + 1); |
| if (!devtype) |
| return -ENOMEM; |
| |
| subsystem = strndup(optarg, slash - optarg); |
| } else |
| subsystem = strdup(optarg); |
| |
| if (!subsystem) |
| return -ENOMEM; |
| |
| r = hashmap_ensure_allocated(&arg_subsystem_filter, NULL); |
| if (r < 0) |
| return r; |
| |
| r = hashmap_put(arg_subsystem_filter, subsystem, devtype); |
| if (r < 0) |
| return r; |
| |
| subsystem = devtype = NULL; |
| break; |
| } |
| case 't': { |
| _cleanup_free_ char *tag = NULL; |
| |
| r = set_ensure_allocated(&arg_tag_filter, &string_hash_ops); |
| if (r < 0) |
| return r; |
| |
| tag = strdup(optarg); |
| if (!tag) |
| return -ENOMEM; |
| |
| r = set_put(arg_tag_filter, tag); |
| if (r < 0) |
| return r; |
| |
| tag = NULL; |
| break; |
| } |
| case 'V': |
| return print_version(); |
| case 'h': |
| return help(); |
| case '?': |
| return -EINVAL; |
| default: |
| assert_not_reached("Unknown option."); |
| } |
| |
| if (!arg_print_kernel && !arg_print_udev) { |
| arg_print_kernel = true; |
| arg_print_udev = true; |
| } |
| |
| return 1; |
| } |
| |
| int monitor_main(int argc, char *argv[], void *userdata) { |
| _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *kernel_monitor = NULL, *udev_monitor = NULL; |
| _cleanup_(sd_event_unrefp) sd_event *event = NULL; |
| int r; |
| |
| r = parse_argv(argc, argv); |
| if (r <= 0) |
| goto finalize; |
| |
| if (running_in_chroot() > 0) { |
| log_info("Running in chroot, ignoring request."); |
| return 0; |
| } |
| |
| /* Callers are expecting to see events as they happen: Line buffering */ |
| setlinebuf(stdout); |
| |
| r = sd_event_default(&event); |
| if (r < 0) { |
| log_error_errno(r, "Failed to initialize event: %m"); |
| goto finalize; |
| } |
| |
| assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); |
| (void) sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); |
| (void) sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); |
| |
| printf("monitor will print the received events for:\n"); |
| if (arg_print_udev) { |
| r = setup_monitor(MONITOR_GROUP_UDEV, event, &udev_monitor); |
| if (r < 0) |
| goto finalize; |
| |
| printf("UDEV - the event which udev sends out after rule processing\n"); |
| } |
| |
| if (arg_print_kernel) { |
| r = setup_monitor(MONITOR_GROUP_KERNEL, event, &kernel_monitor); |
| if (r < 0) |
| goto finalize; |
| |
| printf("KERNEL - the kernel uevent\n"); |
| } |
| printf("\n"); |
| |
| r = sd_event_loop(event); |
| if (r < 0) { |
| log_error_errno(r, "Failed to run event loop: %m"); |
| goto finalize; |
| } |
| |
| r = 0; |
| |
| finalize: |
| hashmap_free_free_free(arg_subsystem_filter); |
| set_free_free(arg_tag_filter); |
| |
| return r; |
| } |