/*****************************************************************************\ * _ _ _ _ ___ * * | || | ___ | |_ _ __ | | _ _ __ _ |_ ) * * | __ |/ _ \| _|| '_ \| || || |/ _` | / / * * |_||_|\___/ \__|| .__/|_| \_,_|\__, |/___| * * |_| |___/ * \*****************************************************************************/ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mem_utils.h" #include "hotplug2.h" #include "parser_utils.h" #define MODALIAS_MAX_LEN 1024 #ifndef KEY_MIN_INTERESTING #define KEY_MIN_INTERESTING KEY_MUTE #endif /* Some kernel headers appear to define it even without __KERNEL__ */ #ifndef BITS_PER_LONG #define BITS_PER_LONG (sizeof(long) * 8) #endif #ifndef NBITS #define NBITS(x) ((x / BITS_PER_LONG) + 1) #endif #define TEST_INPUT_BIT(i,bm) (bm[i / BITS_PER_LONG] & (((unsigned long)1) << (i%BITS_PER_LONG))) int init_netlink_socket() { int netlink_socket; struct sockaddr_nl snl; int buffersize = 16 * 1024 * 1024; memset(&snl, 0x00, sizeof(struct sockaddr_nl)); snl.nl_family = AF_NETLINK; snl.nl_pid = getpid(); snl.nl_groups = 1; netlink_socket = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); if (netlink_socket == -1) { ERROR("opening netlink","Failed socket: %s.", strerror(errno)); return -1; } if (setsockopt(netlink_socket, SOL_SOCKET, SO_SNDBUFFORCE, &buffersize, sizeof(buffersize))) { ERROR("opening netlink","Failed setsockopt: %s. (non-critical)", strerror(errno)); /* Somewhat safe default. */ buffersize = 106496; if (setsockopt(netlink_socket, SOL_SOCKET, SO_SNDBUF, &buffersize, sizeof(buffersize))) { ERROR("opening netlink","Failed setsockopt: %s. (critical)", strerror(errno)); } } if (connect(netlink_socket, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl))) { ERROR("opening netlink","Failed bind: %s.", strerror(errno)); close(netlink_socket); return -1; } return netlink_socket; } inline event_seqnum_t get_kernel_seqnum() { FILE *fp; char filename[64]; char seqnum[64]; strcpy(filename, sysfs_seqnum_path); fp = fopen(filename, "r"); if (fp == NULL) return 0; fread(seqnum, 1, 64, fp); fclose(fp); return strtoull(seqnum, NULL, 0); } char *bitmap_to_bitstring(char name, unsigned long *bm, unsigned int min_bit, unsigned int max_bit) { char *rv; unsigned int i, len = 0, size = 16, srv; rv = xmalloc(size); len += snprintf(rv + len, size - len, "%c", name); for (i = min_bit; i < max_bit; i++) { if (TEST_INPUT_BIT(i, bm)) { while ((srv = snprintf(rv + len, size - len, "%X,", i)) >= (size - len)) { size = size * 2; rv = xrealloc(rv, size); } len += srv; } } return rv; } void string_to_bitmap(char *input, unsigned long *bitmap, int bm_len) { char *token, *ptr; int i = 0; ptr = input + strlen(input); while ((token = dup_token_r(ptr, input, &ptr, isspace)) != NULL) { bitmap[i] = strtoul(token, NULL, 16); free(token); i++; } while (i < bm_len) bitmap[i++] = 0; } #define GET_BITMAP(mapkey, bitmap, name, min) \ if (TEST_INPUT_BIT(EV_ ## mapkey, ev_bits)) { \ token = getenv(#mapkey); \ if (token == NULL) \ return -1; \ \ string_to_bitmap(token, bitmap ## _bits, NBITS(mapkey ## _MAX)); \ } \ bitmap = bitmap_to_bitstring(name, bitmap ## _bits, min, mapkey ## _MAX); int get_input_modalias(char *modalias, int modalias_len) { char *product_env; char *ptr; char *token; unsigned int bustype, vendor, product, version; char *ev, *key, *rel, *abs, *sw, *msc, *led, *snd, *ff; unsigned long ev_bits[NBITS(EV_MAX)]; unsigned long key_bits[NBITS(KEY_MAX)]; unsigned long rel_bits[NBITS(REL_MAX)]; unsigned long abs_bits[NBITS(ABS_MAX)]; unsigned long msc_bits[NBITS(MSC_MAX)]; unsigned long led_bits[NBITS(LED_MAX)]; unsigned long snd_bits[NBITS(SND_MAX)]; unsigned long ff_bits[NBITS(FF_MAX)]; #if defined(SW_MAX) && defined(EV_SW) unsigned long sw_bits[NBITS(SW_MAX)]; #endif memset(ev_bits, 0, NBITS(EV_MAX) * sizeof(long)); memset(key_bits, 0, NBITS(KEY_MAX) * sizeof(long)); memset(rel_bits, 0, NBITS(REL_MAX) * sizeof(long)); memset(abs_bits, 0, NBITS(ABS_MAX) * sizeof(long)); memset(msc_bits, 0, NBITS(MSC_MAX) * sizeof(long)); memset(led_bits, 0, NBITS(LED_MAX) * sizeof(long)); memset(snd_bits, 0, NBITS(SND_MAX) * sizeof(long)); memset(ff_bits, 0, NBITS(FF_MAX) * sizeof(long)); #if defined(SW_MAX) && defined(EV_SW) memset(sw_bits, 0, NBITS(SW_MAX) * sizeof(long)); #endif product_env = getenv("PRODUCT"); if (product_env == NULL) return -1; /* PRODUCT */ ptr = strchr(product_env, '/'); if (ptr == NULL || ptr[1] == '\0') return -1; bustype = strtoul(product_env, NULL, 16); vendor = strtoul(ptr+1, NULL, 16); ptr = strchr(ptr+1, '/'); if (ptr == NULL || ptr[1] == '\0') return -1; product = strtoul(ptr+1, NULL, 16); ptr = strchr(ptr+1, '/'); if (ptr == NULL || ptr[1] == '\0') return -1; version = strtoul(ptr+1, NULL, 16); /* EV */ token = getenv("EV"); if (token == NULL) return -1; string_to_bitmap(token, ev_bits, NBITS(EV_MAX)); ev = bitmap_to_bitstring('e', ev_bits, 0, EV_MAX); GET_BITMAP(KEY, key, 'k', KEY_MIN_INTERESTING); GET_BITMAP(REL, rel, 'r', 0); GET_BITMAP(ABS, abs, 'a', 0); GET_BITMAP(MSC, msc, 'm', 0); GET_BITMAP(LED, led, 'l', 0); GET_BITMAP(SND, snd, 's', 0); GET_BITMAP(FF, ff, 'f', 0); #if defined(SW_MAX) && defined(EV_SW) GET_BITMAP(SW, sw, 'w', 0); #else sw=strdup(""); #endif snprintf(modalias, modalias_len, "MODALIAS=input:b%04Xv%04Xp%04Xe%04X-" "%s%s%s%s%s%s%s%s%s", bustype, vendor, product, version, ev, key, rel, abs, msc, led, snd, ff, sw); /* Ugly but straightforward*/ free(ev); free(key); free(rel); free(abs); free(msc); free(led); free(snd); free(ff); free(sw); return 0; } #undef NBITS #undef TEST_INPUT_BIT int get_pci_modalias(char *modalias, int modalias_len) { char *class_env, *id_env, *subsys_env; char *ptr; unsigned long vendor, device, sub_vendor, sub_device, class_type; unsigned char baseclass, subclass, interface; id_env = getenv("PCI_ID"); subsys_env = getenv("PCI_SUBSYS_ID"); class_env = getenv("PCI_CLASS"); if (id_env == NULL || subsys_env == NULL || class_env == NULL) return -1; if (strlen(id_env) < 9 || strlen(subsys_env) < 9) return -1; /* PCI_ID */ ptr = strchr(id_env, ':'); if (ptr == NULL || ptr[1] == '\0') return -1; vendor = strtoul(id_env, NULL, 16); device = strtoul(ptr+1, NULL, 16); /* PCI_SUBSYS_ID */ ptr = strchr(subsys_env, ':'); if (ptr == NULL || ptr[1] == '\0') return -1; sub_vendor = strtoul(id_env, NULL, 16); sub_device = strtoul(ptr+1, NULL, 16); /* PCI_CLASS */ class_type = strtoul(class_env, NULL, 16); baseclass = (unsigned char)(class_type >> 16); subclass = (unsigned char)(class_type >> 8); interface = (unsigned char)class_type; snprintf(modalias, modalias_len, "MODALIAS=pci:v%08lXd%08lXsv%08lXsd%08lXbc%02Xsc%02Xi%02X", vendor, device, sub_vendor, sub_device, baseclass, subclass, interface); return 0; } int get_ieee1394_modalias(char *modalias, int modalias_len) { char *vendor_env, *model_env; char *specifier_env, *version_env; unsigned long vendor, model; unsigned long specifier, version; vendor_env = getenv("VENDOR_ID"); model_env = getenv("MODEL_ID"); specifier_env = getenv("SPECIFIER_ID"); version_env = getenv("VERSION"); if (vendor_env == NULL || model_env == NULL || specifier_env == NULL || version_env == NULL) return -1; vendor = strtoul(vendor_env, NULL, 16); model = strtoul(model_env, NULL, 16); specifier = strtoul(specifier_env, NULL, 16); version = strtoul(version_env, NULL, 16); snprintf(modalias, modalias_len, "MODALIAS=ieee1394:ven%08lXmo%08lXsp%08lXver%08lX", vendor, model, specifier, version); return 0; } int get_serio_modalias(char *modalias, int modalias_len) { char *serio_type_env, *serio_proto_env; char *serio_id_env, *serio_extra_env; unsigned int serio_type, serio_proto; unsigned int serio_specifier, serio_version; serio_type_env = getenv("SERIO_TYPE"); serio_proto_env = getenv("SERIO_PROTO"); serio_id_env = getenv("SERIO_ID"); serio_extra_env = getenv("SERIO_EXTRA"); if (serio_type_env == NULL || serio_proto_env == NULL || serio_id_env == NULL || serio_extra_env == NULL) return -1; serio_type = strtoul(serio_type_env, NULL, 16); serio_proto = strtoul(serio_proto_env, NULL, 16); serio_specifier = strtoul(serio_id_env, NULL, 16); serio_version = strtoul(serio_extra_env, NULL, 16); snprintf(modalias, modalias_len, "MODALIAS=serio:ty%02Xpr%02Xid%02Xex%02X", serio_type, serio_proto, serio_specifier, serio_version); return 0; } int get_usb_modalias(char *modalias, int modalias_len) { char *product_env, *type_env, *interface_env; char *ptr; unsigned int idVendor, idProduct, bcdDevice; unsigned int device_class, device_subclass, device_protocol; unsigned int interface_class, interface_subclass, interface_protocol; product_env = getenv("PRODUCT"); type_env = getenv("TYPE"); interface_env = getenv("INTERFACE"); if (product_env == NULL || type_env == NULL) return -1; /* PRODUCT */ ptr = strchr(product_env, '/'); if (ptr == NULL || ptr[1] == '\0') return -1; idVendor = strtoul(product_env, NULL, 16); idProduct = strtoul(ptr+1, NULL, 16); ptr = strchr(ptr+1, '/'); if (ptr == NULL || ptr[1] == '\0') return -1; bcdDevice = strtoul(ptr+1, NULL, 16); /* TYPE */ ptr = strchr(type_env, '/'); if (ptr == NULL || ptr[1] == '\0') return -1; device_class = strtoul(type_env, NULL, 10); device_subclass = strtoul(ptr+1, NULL, 10); ptr = strchr(ptr+1, '/'); if (ptr == NULL || ptr[1] == '\0') return -1; device_protocol = strtoul(ptr+1, NULL, 10); /* INTERFACE */ if (interface_env != NULL) { ptr = strchr(interface_env, '/'); if (ptr == NULL || ptr[1] == '\0') return -1; interface_class = strtoul(interface_env, NULL, 10); interface_subclass = strtoul(ptr+1, NULL, 10); ptr = strchr(ptr+1, '/'); if (ptr == NULL || ptr[1] == '\0') return -1; interface_protocol = strtoul(ptr+1, NULL, 10); snprintf(modalias, modalias_len, "MODALIAS=usb:v%04Xp%04Xd%04X" "dc%02Xdsc%02Xdp%02Xic%02Xisc%02Xip%02X", idVendor, idProduct, bcdDevice, device_class, device_subclass, device_protocol, interface_class, interface_subclass, interface_protocol); } else { snprintf(modalias, modalias_len, "MODALIAS=usb:v%04Xp%04Xd%04X" "dc%02Xdsc%02Xdp%02Xic*isc*ip*", idVendor, idProduct, bcdDevice, device_class, device_subclass, device_protocol); } return 0; } int get_modalias(char *bus, char *modalias, int modalias_len) { memset(modalias, 0, modalias_len); if (!strcmp(bus, "pci")) return get_pci_modalias(modalias, modalias_len); if (!strcmp(bus, "usb")) return get_usb_modalias(modalias, modalias_len); if (!strcmp(bus, "ieee1394")) return get_ieee1394_modalias(modalias, modalias_len); if (!strcmp(bus, "serio")) return get_serio_modalias(modalias, modalias_len); if (!strcmp(bus, "input")) return get_input_modalias(modalias, modalias_len); /* 'ccw' devices do not generate events, we do not need to handle them */ /* 'of' devices do not generate events either */ /* 'pnp' devices do generate events, but they lack any device */ /* description whatsoever. */ return -1; } inline char *get_uevent_string(char **environ, char *bus, unsigned long *uevent_string_len) { char *uevent_string; char *tmp; char modalias[MODALIAS_MAX_LEN]; unsigned long offset; tmp = getenv("ACTION"); if (tmp == NULL) return NULL; *uevent_string_len = strlen(tmp) + 1; offset = *uevent_string_len - 1; uevent_string = xmalloc(*uevent_string_len); strcpy(uevent_string, tmp); uevent_string[offset] = '@'; offset++; for (; *environ != NULL; environ++) { *uevent_string_len += strlen(*environ) + 1; uevent_string = xrealloc(uevent_string, *uevent_string_len); strcpy(&uevent_string[offset], *environ); offset = *uevent_string_len; } if (getenv("SEQNUM") == NULL) { /* 64 + 7 ('SEQNUM=') + 1 ('\0') */ tmp = xmalloc(72); memset(tmp, 0, 72); snprintf(tmp, 72, "SEQNUM=%llu", get_kernel_seqnum()); *uevent_string_len += strlen(tmp) + 1; uevent_string = xrealloc(uevent_string, *uevent_string_len); strcpy(&uevent_string[offset], tmp); offset = *uevent_string_len; free(tmp); } if (getenv("SUBSYSTEM") == NULL) { /* 10 ('SUBSYSTEM=') + 1 ('\0') */ tmp = xmalloc(11 + strlen(bus)); strcpy(tmp, "SUBSYSTEM="); strcat(tmp+10, bus); *uevent_string_len += strlen(tmp) + 1; uevent_string = xrealloc(uevent_string, *uevent_string_len); strcpy(&uevent_string[offset], tmp); offset = *uevent_string_len; free(tmp); } /* Only create our own MODALIAS if we do not have one set... */ if (getenv("MODALIAS") == NULL) { if (!get_modalias(bus, modalias, MODALIAS_MAX_LEN)) { *uevent_string_len += strlen(modalias) + 1; uevent_string = xrealloc(uevent_string, *uevent_string_len); strcpy(&uevent_string[offset], modalias); offset = *uevent_string_len; } } return uevent_string; } int main(int argc, char *argv[], char **environ) { char *uevent_string; unsigned long uevent_string_len; int netlink_socket; if (argv[1] == NULL) { ERROR("parsing arguments", "Malformed event arguments (bus missing)."); return 1; } uevent_string = get_uevent_string(environ, argv[1], &uevent_string_len); if (uevent_string == NULL) { ERROR("parsing env vars", "Malformed event environmental variables."); return 1; } netlink_socket = init_netlink_socket(); if (netlink_socket == -1) { ERROR("netlink init","Unable to open netlink socket."); goto exit; } if (send(netlink_socket, uevent_string, uevent_string_len, 0) == -1) { ERROR("sending data","send failed: %s.", strerror(errno)); } close(netlink_socket); exit: free(uevent_string); return 0; }