/* * (C) 2011-2024 by Christian Hesse <mail@eworm.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ #include "udev-block-notify.h" #include "config.h" #include "version.h" const static char optstring[] = "ht:vV"; const static struct option options_long[] = { /* name has_arg flag val */ { "help", no_argument, NULL, 'h' }, { "timeout", required_argument, NULL, 't' }, { "verbose", no_argument, NULL, 'v' }, { "version", no_argument, NULL, 'V' }, { 0, 0, 0, 0 } }; char *program; uint8_t verbose = 0; uint8_t doexit = 0; /*** get_notification ***/ NotifyNotification * get_notification(struct notifications *notifications, dev_t devnum) { /* work with notifications->next here, we need it later to not miss * the pointer in main struct */ while (notifications->next != NULL) { if (notifications->next->devnum == devnum) return notifications->next->notification; notifications = notifications->next; } /* did not find the notification, creating a new struct element */ notifications->next = malloc(sizeof(struct notifications)); notifications = notifications->next; notifications->devnum = devnum; notifications->notification = # if NOTIFY_CHECK_VERSION(0, 7, 0) notify_notification_new(NULL, NULL, NULL); # else notify_notification_new(NULL, NULL, NULL, NULL); # endif notifications->next = NULL; notify_notification_set_category(notifications->notification, PROGNAME); notify_notification_set_urgency (notifications->notification, NOTIFY_URGENCY_NORMAL); return notifications->notification; } /*** newstr ***/ char * newstr(const char *text, const char *device, unsigned short int major, unsigned short int minor) { char *notifystr; notifystr = malloc(strlen(text) + strlen(device) + 10 /* max string length 2* unsigned short int */); sprintf(notifystr, text, device, major, minor); return notifystr; } /*** appendstr ***/ char * appendstr(const char *text, char *notifystr, const char *property, const char *value) { notifystr = realloc(notifystr, strlen(text) + strlen(notifystr) + strlen(property) + strlen(value)); sprintf(notifystr + strlen(notifystr), text, property, value); return notifystr; } /*** received_signal ***/ void received_signal(int signal) { if (verbose > 0) printf("%s: Received signal: %s\n", program, strsignal(signal)); doexit++; } /*** main ***/ int main (int argc, char ** argv) { const char *action = NULL; const char *device = NULL, *value = NULL; char *icon = NULL, *notifystr = NULL; fd_set readfds; GError *error = NULL; NotifyNotification *notification = NULL; struct notifications *notifications = NULL; unsigned int notification_timeout = NOTIFICATION_TIMEOUT; int i; dev_t devnum = 0; unsigned int major = 0, minor = 0; struct udev_device *dev = NULL; struct udev_monitor *mon = NULL; struct udev *udev = NULL; unsigned int version = 0, help = 0; program = argv[0]; /* get the verbose status */ while ((i = getopt_long(argc, argv, optstring, options_long, NULL)) != -1) { switch (i) { case 'h': help++; break; case 't': notification_timeout = atof(optarg) * 1000; break; case 'v': verbose++; break; case 'V': verbose++; version++; break; } } if (verbose > 0) printf("%s: %s v%s" #ifdef HAVE_SYSTEMD " +systemd" #endif " (compiled: " __DATE__ ", " __TIME__ ")\n", program, PROGNAME, VERSION); if (help > 0) printf("usage: %s [-h] [-t TIMEOUT] [-v] [-V]\n", program); if (version > 0 || help > 0) return EXIT_SUCCESS; if(notify_init("Udev-Block-Notification") == FALSE) { fprintf(stderr, "%s: Can't create notify.\n", program); exit(EXIT_FAILURE); } if ((udev = udev_new()) == NULL) { fprintf(stderr, "%s: Can't create udev.\n", program); exit(EXIT_FAILURE); } mon = udev_monitor_new_from_netlink(udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(mon, "block", NULL); udev_monitor_enable_receiving(mon); /* allocate first struct element as dummy */ notifications = malloc(sizeof(struct notifications)); notifications->devnum = 0; notifications->notification = NULL; notifications->next = NULL; struct sigaction act = { 0 }; act.sa_handler = received_signal; sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); #ifdef HAVE_SYSTEMD sd_notify(0, "READY=1\nSTATUS=Waiting for udev block events..."); #endif while (doexit == 0) { FD_ZERO(&readfds); if (mon != NULL) FD_SET(udev_monitor_get_fd(mon), &readfds); select(udev_monitor_get_fd(mon) + 1, &readfds, NULL, NULL, NULL); if ((mon != NULL) && FD_ISSET(udev_monitor_get_fd(mon), &readfds)) { dev = udev_monitor_receive_device(mon); if(dev) { device = udev_device_get_sysname(dev); /* ignore temporary device mapper devices * is there a better way to do this? */ if (strncmp(device, "dm", 2) == 0) { const char * property; if (udev_device_get_property_value(dev, "DM_NAME") == NULL) { if (verbose > 0) printf("%s: Skipping temporary DM device %s\n", program, device); continue; } if ((property = udev_device_get_property_value(dev, "DM_LV_LAYER")) != NULL) { if (strcmp(property, "cow") == 0 || strcmp(property, "real") == 0) { if (verbose > 0) printf("%s: Skipping DM %s device %s\n", program, property, device); continue; } } } devnum = udev_device_get_devnum(dev); major = major(devnum); minor = minor(devnum); if (verbose > 0) printf("%s: Processing device %d:%d\n", program, major, minor); action = udev_device_get_action(dev); if (strcmp(action, "add") == 0) notifystr = newstr(TEXT_ADD, device, major, minor); else if (strcmp(action, "remove") == 0) notifystr = newstr(TEXT_REMOVE, device, major, minor); else if (strcmp(action, "move") == 0) notifystr = newstr(TEXT_MOVE, device, major, minor); else if (strcmp(action, "change") == 0) notifystr = newstr(TEXT_CHANGE, device, major, minor); else /* we should never get here I think... */ notifystr = newstr(TEXT_DEFAULT, device, major, minor); /* Get possible values with: * $ udevadm info --query=all --name=/path/to/dev * Values available differs from device type and content */ /* file system */ if ((value = udev_device_get_property_value(dev, "ID_FS_LABEL")) != NULL && *value != 0) notifystr = appendstr(TEXT_TAG, notifystr, "Label", value); if ((value = udev_device_get_property_value(dev, "ID_FS_TYPE")) != NULL && *value != 0) notifystr = appendstr(TEXT_TAG, notifystr, "Type", value); if ((value = udev_device_get_property_value(dev, "ID_FS_USAGE")) != NULL && *value != 0) notifystr = appendstr(TEXT_TAG, notifystr, "Usage", value); if ((value = udev_device_get_property_value(dev, "ID_FS_UUID")) != NULL && *value != 0) notifystr = appendstr(TEXT_TAG, notifystr, "UUID", value); /* partition */ if ((value = udev_device_get_property_value(dev, "ID_PART_TABLE_TYPE")) != NULL && *value != 0) notifystr = appendstr(TEXT_TAG, notifystr, "Partition Table Type", value); if ((value = udev_device_get_property_value(dev, "ID_PART_TABLE_NAME")) != NULL && *value != 0) notifystr = appendstr(TEXT_TAG, notifystr, "Partition Name", value); if ((value = udev_device_get_property_value(dev, "ID_PART_ENTRY_TYPE")) != NULL && *value != 0) notifystr = appendstr(TEXT_TAG, notifystr, "Partition Type", value); /* device mapper */ if ((value = udev_device_get_property_value(dev, "DM_NAME")) != NULL && *value != 0) notifystr = appendstr(TEXT_TAG, notifystr, "Device mapper name", value); /* multi disk */ if ((value = udev_device_get_property_value(dev, "MD_LEVEL")) != NULL && *value != 0) notifystr = appendstr(TEXT_TAG, notifystr, "Multi disk level", value); if (verbose > 0) printf("%s: %s\n", program, notifystr); /* get a notification */ notification = get_notification(notifications, devnum); /* this is a fallback and should be replaced below * if it is not... what drive is it? */ icon = ICON_UNKNOWN; /* decide about what icon to use */ value = udev_device_get_property_value(dev, "ID_BUS"); if (udev_device_get_property_value(dev, "ID_CDROM") != NULL) { /* optical drive */ if (udev_device_get_property_value(dev, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO") != NULL) { icon = ICON_MEDIA_OPTICAL_CD_AUDIO; } else { icon = ICON_DRIVE_OPTICAL; } } else if (udev_device_get_property_value(dev, "ID_DRIVE_FLOPPY") != NULL) { /* floppy drive */ icon = ICON_MEDIA_FLOPPY; } else if (udev_device_get_property_value(dev, "ID_DRIVE_THUMB") != NULL) { /* thumb drive, e.g. USB flash */ icon = ICON_MEDIA_REMOVABLE; } else if (udev_device_get_property_value(dev, "ID_DRIVE_FLASH_CF") != NULL || udev_device_get_property_value(dev, "ID_DRIVE_FLASH_MS") != NULL || udev_device_get_property_value(dev, "ID_DRIVE_FLASH_SD") != NULL || udev_device_get_property_value(dev, "ID_DRIVE_FLASH_SM") != NULL) { /* flash card reader */ /* note that usb card reader are recognized as USB hard disk */ icon = ICON_MEDIA_FLASH; } else if (udev_device_get_property_value(dev, "ID_DRIVE_FLOPPY_ZIP") != NULL) { /* ZIP drive */ icon = ICON_MEDIA_ZIP; } else if (udev_device_get_property_value(dev, "ID_MEDIA_PLAYER") != NULL) { /* media player */ icon = ICON_MULTIMEDIA_PLAYER; } else if (udev_device_get_property_value(dev, "DM_NAME") != NULL) { /* device mapper */ icon = ICON_DEVICE_MAPPER; } else if (udev_device_get_property_value(dev, "MD_NAME") != NULL) { /* multi disk */ icon = ICON_DRIVE_MULTIDISK; } else if (strncmp(device, "loop", 4) == 0 || strncmp(device, "ram", 3) == 0) { /* loop & RAM */ icon = ICON_LOOP; } else if (strncmp(device, "nbd", 3) == 0) { /* network block device */ icon = ICON_NETWORK_SERVER; } else if (value != NULL) { if (strcmp(value, "ata") == 0 || strcmp(value, "scsi") == 0) { /* internal (s)ata/scsi hard disk */ icon = ICON_DRIVE_HARDDISK; } else if (strcmp(value, "usb") == 0) { /* USB hard disk */ icon = ICON_DRIVE_HARDDISK_USB; } else if (strcmp(value, "ieee1394") == 0) { /* firewire hard disk */ icon = ICON_DRIVE_HARDDISK_IEEE1394; } } notify_notification_update(notification, TEXT_TOPIC, notifystr, icon); notify_notification_set_timeout(notification, notification_timeout); if (notify_notification_show(notification, &error) == FALSE) { g_printerr("%s: Error showing notification: %s\n", program, error->message); g_error_free(error); exit(EXIT_FAILURE); } free(notifystr); udev_device_unref(dev); } // This is not really needed... But we want to make shure not to eat 100% CPU if anything breaks. ;) usleep(50 * 1000); } } /* report stopping to systemd */ #ifdef HAVE_SYSTEMD sd_notify(0, "STOPPING=1\nSTATUS=Stopping..."); #endif udev_unref(udev); #ifdef HAVE_SYSTEMD sd_notify(0, "STATUS=Stopped. Bye!"); #endif return EXIT_SUCCESS; }