Send SUBSYSTEM=power_supply NETLINK events to an addtional group

Our Android Emulator runs on x86 and the defconfig includes
CONFIG_X86_CPUID. This driver calls device_destroy in
the teardown callback (from cpuhp_setup_state).

During suspending, the kernel destroys cpus and broadcasts
NETLINK events about it to nl_groups=1 (as all other events).

healthd listens for all NETLINK messages (to filter only
SUBSYSTEM=power_supply ones) with EPOLLWAKEUP to react urgently
for battery issues.

Since the suspending process produces cpu shutdown events,
healthd always prevents our system from suspending.

This change should be matched with a healthd change to stop
listening for nl_groups=1.

Bug: 139203596
Test: CtsVerifier, SENSORS/Device Suspend Test
Change-Id: Ia2463cbc83f65c54e1f2d50864b3e103d83418d8
Signed-off-by: Roman Kiryanov <rkir@google.com>
diff --git a/lib/kobject_uevent.c b/lib/kobject_uevent.c
index f237a09..1c6eda4 100644
--- a/lib/kobject_uevent.c
+++ b/lib/kobject_uevent.c
@@ -338,6 +338,7 @@
 	int retval = 0;
 #ifdef CONFIG_NET
 	struct uevent_sock *ue_sk;
+	u32 dst_nl_group = 0;
 #endif
 
 	pr_debug("kobject: '%s' (%p): %s\n",
@@ -462,6 +463,9 @@
 	}
 
 #if defined(CONFIG_NET)
+	dst_nl_group =
+		(strcmp(subsystem, "power_supply") == 0) ? (1u << 16) : 0;
+
 	/* send netlink message */
 	list_for_each_entry(ue_sk, &uevent_sock_list, list) {
 		struct sock *uevent_sock = ue_sk->sk;
@@ -490,15 +494,59 @@
 
 			NETLINK_CB(skb).dst_group = 1;
 			retval = netlink_broadcast_filtered(uevent_sock, skb,
-							    0, 1, GFP_KERNEL,
-							    kobj_bcast_filter,
-							    kobj);
+				0, 1, GFP_KERNEL,
+				kobj_bcast_filter,
+				kobj);
 			/* ENOBUFS should be handled in userspace */
 			if (retval == -ENOBUFS || retval == -ESRCH)
 				retval = 0;
 		} else
 			retval = -ENOMEM;
 	}
+
+	if (dst_nl_group) {
+		/* send netlink message */
+		list_for_each_entry(ue_sk, &uevent_sock_list, list) {
+			struct sock *uevent_sock = ue_sk->sk;
+			struct sk_buff *skb;
+			size_t len;
+
+			if (!netlink_has_listeners(uevent_sock, dst_nl_group))
+				continue;
+
+			/* allocate message with the maximum possible size */
+			len = strlen(action_string) + strlen(devpath) + 2;
+			skb = alloc_skb(len + env->buflen, GFP_KERNEL);
+			if (skb) {
+				char *scratch;
+
+				/* add header */
+				scratch = skb_put(skb, len);
+				sprintf(scratch, "%s@%s", action_string,
+					devpath);
+
+				/* copy keys to our continuous event payload
+				 * buffer
+				 */
+				for (i = 0; i < env->envp_idx; i++) {
+					len = strlen(env->envp[i]) + 1;
+					scratch = skb_put(skb, len);
+					strcpy(scratch, env->envp[i]);
+				}
+
+				NETLINK_CB(skb).dst_group = dst_nl_group;
+				retval = netlink_broadcast_filtered(
+					uevent_sock, skb,
+					0, dst_nl_group, GFP_KERNEL,
+					kobj_bcast_filter,
+					kobj);
+				/* ENOBUFS should be handled in userspace */
+				if (retval == -ENOBUFS || retval == -ESRCH)
+					retval = 0;
+			} else
+				retval = -ENOMEM;
+		}
+	}
 #endif
 	mutex_unlock(&uevent_sock_mutex);