+static inline void bpf_stmt(struct sock_filter *inss, unsigned int *i,
+ unsigned short code, unsigned int data)
+{
+ struct sock_filter *ins = &inss[*i];
+
+ ins->code = code;
+ ins->k = data;
+ (*i)++;
+}
+
+static inline void bpf_jmp(struct sock_filter *inss, unsigned int *i,
+ unsigned short code, unsigned int data,
+ unsigned short jt, unsigned short jf)
+{
+ struct sock_filter *ins = &inss[*i];
+
+ ins->code = code;
+ ins->jt = jt;
+ ins->jf = jf;
+ ins->k = data;
+ (*i)++;
+}
+
+static int filter_apply(struct udev_monitor *udev_monitor)
+{
+ static struct sock_filter ins[256];
+ static struct sock_fprog filter;
+ unsigned int i;
+ struct udev_list_entry *list_entry;
+ int err;
+
+ if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL)
+ return 0;
+
+ memset(ins, 0x00, sizeof(ins));
+ i = 0;
+
+ /* load magic in A */
+ bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, magic));
+ /* jump if magic matches */
+ bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0);
+ /* wrong magic, pass packet */
+ bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff);
+
+ /* add all subsystem match values */
+ udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) {
+ unsigned int hash;
+
+ /* load filter_subsystem value in A */
+ bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_subsystem));
+ hash = util_string_hash32(udev_list_entry_get_name(list_entry));
+ if (udev_list_entry_get_value(list_entry) == NULL) {
+ /* jump if subsystem does not match */
+ bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1);
+ } else {
+ /* jump if subsystem does not match */
+ bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3);
+
+ /* load filter_devtype value in A */
+ bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_devtype));
+ /* jump if value does not match */
+ hash = util_string_hash32(udev_list_entry_get_value(list_entry));
+ bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1);
+ }
+
+ /* matched, pass packet */
+ bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff);
+
+ if (i+1 >= ARRAY_SIZE(ins))
+ return -1;
+ }
+ /* nothing matched, drop packet */
+ bpf_stmt(ins, &i, BPF_RET|BPF_K, 0);
+
+ /* install filter */
+ filter.len = i;
+ filter.filter = ins;
+ err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
+ return err;
+}
+
+int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
+{
+ int err;
+ const int on = 1;
+
+ if (udev_monitor->sun.sun_family != 0) {
+ err = bind(udev_monitor->sock,
+ (struct sockaddr *)&udev_monitor->sun, udev_monitor->addrlen);
+ } else if (udev_monitor->snl.nl_family != 0) {
+ filter_apply(udev_monitor);
+ err = bind(udev_monitor->sock,
+ (struct sockaddr *)&udev_monitor->snl, sizeof(struct sockaddr_nl));
+ } else {
+ return -EINVAL;
+ }
+
+ if (err < 0) {
+ err(udev_monitor->udev, "bind failed: %m\n");
+ return err;
+ }
+
+ /* enable receiving of sender credentials */
+ setsockopt(udev_monitor->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
+ return 0;
+}
+
+int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size)
+{
+ if (udev_monitor == NULL)
+ return -1;
+ return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size));
+}
+