kobject_uevent.c 10.4 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * kernel userspace event delivery
 *
 * Copyright (C) 2004 Red Hat, Inc.  All rights reserved.
 * Copyright (C) 2004 Novell, Inc.  All rights reserved.
 * Copyright (C) 2004 IBM, Inc. All rights reserved.
 *
 * Licensed under the GNU GPL v2.
 *
 * Authors:
 *	Robert Love		<rml@novell.com>
 *	Kay Sievers		<kay.sievers@vrfy.org>
 *	Arjan van de Ven	<arjanv@redhat.com>
 *	Greg Kroah-Hartman	<greg@kroah.com>
 */

#include <linux/spinlock.h>
18 19
#include <linux/string.h>
#include <linux/kobject.h>
20 21
#include <linux/export.h>
#include <linux/kmod.h>
22
#include <linux/slab.h>
23
#include <linux/user_namespace.h>
Linus Torvalds's avatar
Linus Torvalds committed
24 25 26 27
#include <linux/socket.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <net/sock.h>
28
#include <net/net_namespace.h>
Linus Torvalds's avatar
Linus Torvalds committed
29 30


31
u64 uevent_seqnum;
32
char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
33 34 35 36 37 38
#ifdef CONFIG_NET
struct uevent_sock {
	struct list_head list;
	struct sock *sk;
};
static LIST_HEAD(uevent_sock_list);
39 40
#endif

41 42 43
/* This lock protects uevent_seqnum and uevent_sock_list */
static DEFINE_MUTEX(uevent_sock_mutex);

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
/* the strings here must match the enum in include/linux/kobject.h */
static const char *kobject_actions[] = {
	[KOBJ_ADD] =		"add",
	[KOBJ_REMOVE] =		"remove",
	[KOBJ_CHANGE] =		"change",
	[KOBJ_MOVE] =		"move",
	[KOBJ_ONLINE] =		"online",
	[KOBJ_OFFLINE] =	"offline",
};

/**
 * kobject_action_type - translate action string to numeric type
 *
 * @buf: buffer containing the action string, newline is ignored
 * @len: length of buffer
 * @type: pointer to the location to store the action type
 *
 * Returns 0 if the action string was recognized.
 */
int kobject_action_type(const char *buf, size_t count,
			enum kobject_action *type)
{
	enum kobject_action action;
	int ret = -EINVAL;

69
	if (count && (buf[count-1] == '\n' || buf[count-1] == '\0'))
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
		count--;

	if (!count)
		goto out;

	for (action = 0; action < ARRAY_SIZE(kobject_actions); action++) {
		if (strncmp(kobject_actions[action], buf, count) != 0)
			continue;
		if (kobject_actions[action][count] != '\0')
			continue;
		*type = action;
		ret = 0;
		break;
	}
out:
	return ret;
}

88
#ifdef CONFIG_NET
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
static int kobj_bcast_filter(struct sock *dsk, struct sk_buff *skb, void *data)
{
	struct kobject *kobj = data;
	const struct kobj_ns_type_operations *ops;

	ops = kobj_ns_ops(kobj);
	if (ops) {
		const void *sock_ns, *ns;
		ns = kobj->ktype->namespace(kobj);
		sock_ns = ops->netlink_ns(dsk);
		return sock_ns != ns;
	}

	return 0;
}
104
#endif
105

106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
static int kobj_usermode_filter(struct kobject *kobj)
{
	const struct kobj_ns_type_operations *ops;

	ops = kobj_ns_ops(kobj);
	if (ops) {
		const void *init_ns, *ns;
		ns = kobj->ktype->namespace(kobj);
		init_ns = ops->initial_ns();
		return ns != init_ns;
	}

	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
121
/**
122
 * kobject_uevent_env - send an uevent with environmental data
Linus Torvalds's avatar
Linus Torvalds committed
123
 *
124
 * @action: action that is happening
Linus Torvalds's avatar
Linus Torvalds committed
125
 * @kobj: struct kobject that the action is happening to
126
 * @envp_ext: pointer to environmental data
127
 *
128
 * Returns 0 if kobject_uevent_env() is completed with success or the
129
 * corresponding error when it fails.
Linus Torvalds's avatar
Linus Torvalds committed
130
 */
131
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
132
		       char *envp_ext[])
Linus Torvalds's avatar
Linus Torvalds committed
133
{
134 135
	struct kobj_uevent_env *env;
	const char *action_string = kobject_actions[action];
136 137 138 139
	const char *devpath = NULL;
	const char *subsystem;
	struct kobject *top_kobj;
	struct kset *kset;
140
	const struct kset_uevent_ops *uevent_ops;
Linus Torvalds's avatar
Linus Torvalds committed
141
	int i = 0;
142
	int retval = 0;
143 144 145
#ifdef CONFIG_NET
	struct uevent_sock *ue_sk;
#endif
Linus Torvalds's avatar
Linus Torvalds committed
146

147
	pr_debug("kobject: '%s' (%p): %s\n",
148
		 kobject_name(kobj), kobj, __func__);
149 150 151

	/* search the kset we belong to */
	top_kobj = kobj;
152
	while (!top_kobj->kset && top_kobj->parent)
153
		top_kobj = top_kobj->parent;
154

155
	if (!top_kobj->kset) {
156 157
		pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
			 "without kset!\n", kobject_name(kobj), kobj,
158
			 __func__);
159 160
		return -EINVAL;
	}
Linus Torvalds's avatar
Linus Torvalds committed
161

162
	kset = top_kobj->kset;
163
	uevent_ops = kset->uevent_ops;
Linus Torvalds's avatar
Linus Torvalds committed
164

165 166 167 168 169 170 171
	/* skip the event, if uevent_suppress is set*/
	if (kobj->uevent_suppress) {
		pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
				 "caused the event to drop!\n",
				 kobject_name(kobj), kobj, __func__);
		return 0;
	}
172
	/* skip the event, if the filter returns zero. */
173
	if (uevent_ops && uevent_ops->filter)
174
		if (!uevent_ops->filter(kset, kobj)) {
175 176
			pr_debug("kobject: '%s' (%p): %s: filter function "
				 "caused the event to drop!\n",
177
				 kobject_name(kobj), kobj, __func__);
178 179
			return 0;
		}
Linus Torvalds's avatar
Linus Torvalds committed
180

181 182 183 184 185 186
	/* originating subsystem */
	if (uevent_ops && uevent_ops->name)
		subsystem = uevent_ops->name(kset, kobj);
	else
		subsystem = kobject_name(&kset->kobj);
	if (!subsystem) {
187 188
		pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
			 "event to drop!\n", kobject_name(kobj), kobj,
189
			 __func__);
190 191 192
		return 0;
	}

193 194 195
	/* environment buffer */
	env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
	if (!env)
196
		return -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
197

198 199
	/* complete object path */
	devpath = kobject_get_path(kobj, GFP_KERNEL);
200 201
	if (!devpath) {
		retval = -ENOENT;
202
		goto exit;
203
	}
Linus Torvalds's avatar
Linus Torvalds committed
204

205
	/* default keys */
206 207 208 209 210 211 212 213 214 215 216 217 218
	retval = add_uevent_var(env, "ACTION=%s", action_string);
	if (retval)
		goto exit;
	retval = add_uevent_var(env, "DEVPATH=%s", devpath);
	if (retval)
		goto exit;
	retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
	if (retval)
		goto exit;

	/* keys passed in from the caller */
	if (envp_ext) {
		for (i = 0; envp_ext[i]; i++) {
219
			retval = add_uevent_var(env, "%s", envp_ext[i]);
220 221 222 223
			if (retval)
				goto exit;
		}
	}
Linus Torvalds's avatar
Linus Torvalds committed
224

225
	/* let the kset specific function add its stuff */
226
	if (uevent_ops && uevent_ops->uevent) {
227
		retval = uevent_ops->uevent(kset, kobj, env);
Linus Torvalds's avatar
Linus Torvalds committed
228
		if (retval) {
229 230
			pr_debug("kobject: '%s' (%p): %s: uevent() returned "
				 "%d\n", kobject_name(kobj), kobj,
231
				 __func__, retval);
Linus Torvalds's avatar
Linus Torvalds committed
232 233 234 235
			goto exit;
		}
	}

236 237 238 239 240 241 242 243 244 245 246
	/*
	 * Mark "add" and "remove" events in the object to ensure proper
	 * events to userspace during automatic cleanup. If the object did
	 * send an "add" event, "remove" will automatically generated by
	 * the core, if not already done by the caller.
	 */
	if (action == KOBJ_ADD)
		kobj->state_add_uevent_sent = 1;
	else if (action == KOBJ_REMOVE)
		kobj->state_remove_uevent_sent = 1;

247
	mutex_lock(&uevent_sock_mutex);
248
	/* we will send an event, so request a new sequence number */
249 250 251
	retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
	if (retval) {
		mutex_unlock(&uevent_sock_mutex);
252
		goto exit;
253
	}
Linus Torvalds's avatar
Linus Torvalds committed
254

255
#if defined(CONFIG_NET)
256
	/* send netlink message */
257 258
	list_for_each_entry(ue_sk, &uevent_sock_list, list) {
		struct sock *uevent_sock = ue_sk->sk;
259 260 261
		struct sk_buff *skb;
		size_t len;

262 263 264
		if (!netlink_has_listeners(uevent_sock, 1))
			continue;

265 266
		/* allocate message with the maximum possible size */
		len = strlen(action_string) + strlen(devpath) + 2;
267
		skb = alloc_skb(len + env->buflen, GFP_KERNEL);
268
		if (skb) {
269 270
			char *scratch;

271 272 273 274 275
			/* add header */
			scratch = skb_put(skb, len);
			sprintf(scratch, "%s@%s", action_string, devpath);

			/* copy keys to our continuous event payload buffer */
276 277
			for (i = 0; i < env->envp_idx; i++) {
				len = strlen(env->envp[i]) + 1;
278
				scratch = skb_put(skb, len);
279
				strcpy(scratch, env->envp[i]);
280 281 282
			}

			NETLINK_CB(skb).dst_group = 1;
283 284 285 286
			retval = netlink_broadcast_filtered(uevent_sock, skb,
							    0, 1, GFP_KERNEL,
							    kobj_bcast_filter,
							    kobj);
287
			/* ENOBUFS should be handled in userspace */
288
			if (retval == -ENOBUFS || retval == -ESRCH)
289
				retval = 0;
290 291
		} else
			retval = -ENOMEM;
292
	}
293
#endif
294
	mutex_unlock(&uevent_sock_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
295

296
	/* call uevent_helper, usually only enabled during early boot */
297
	if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
298
		char *argv [3];
Linus Torvalds's avatar
Linus Torvalds committed
299

300
		argv [0] = uevent_helper;
301 302
		argv [1] = (char *)subsystem;
		argv [2] = NULL;
303 304 305
		retval = add_uevent_var(env, "HOME=/");
		if (retval)
			goto exit;
306 307
		retval = add_uevent_var(env,
					"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
308 309 310
		if (retval)
			goto exit;

311
		retval = call_usermodehelper(argv[0], argv,
312
					     env->envp, UMH_WAIT_EXEC);
313
	}
Linus Torvalds's avatar
Linus Torvalds committed
314 315

exit:
316
	kfree(devpath);
317
	kfree(env);
318
	return retval;
Linus Torvalds's avatar
Linus Torvalds committed
319
}
320 321 322
EXPORT_SYMBOL_GPL(kobject_uevent_env);

/**
323
 * kobject_uevent - notify userspace by sending an uevent
324
 *
325
 * @action: action that is happening
326
 * @kobj: struct kobject that the action is happening to
327 328 329
 *
 * Returns 0 if kobject_uevent() is completed with success or the
 * corresponding error when it fails.
330
 */
331
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
332
{
333
	return kobject_uevent_env(kobj, action, NULL);
334
}
335
EXPORT_SYMBOL_GPL(kobject_uevent);
Linus Torvalds's avatar
Linus Torvalds committed
336 337

/**
338 339 340
 * add_uevent_var - add key value string to the environment buffer
 * @env: environment buffer structure
 * @format: printf format for the key=value pair
Linus Torvalds's avatar
Linus Torvalds committed
341 342 343 344
 *
 * Returns 0 if environment variable was added successfully or -ENOMEM
 * if no space was available.
 */
345
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)
Linus Torvalds's avatar
Linus Torvalds committed
346 347
{
	va_list args;
348
	int len;
Linus Torvalds's avatar
Linus Torvalds committed
349

350
	if (env->envp_idx >= ARRAY_SIZE(env->envp)) {
Arjan van de Ven's avatar
Arjan van de Ven committed
351
		WARN(1, KERN_ERR "add_uevent_var: too many keys\n");
Linus Torvalds's avatar
Linus Torvalds committed
352
		return -ENOMEM;
353
	}
Linus Torvalds's avatar
Linus Torvalds committed
354 355

	va_start(args, format);
356 357 358
	len = vsnprintf(&env->buf[env->buflen],
			sizeof(env->buf) - env->buflen,
			format, args);
Linus Torvalds's avatar
Linus Torvalds committed
359 360
	va_end(args);

361
	if (len >= (sizeof(env->buf) - env->buflen)) {
Arjan van de Ven's avatar
Arjan van de Ven committed
362
		WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n");
Linus Torvalds's avatar
Linus Torvalds committed
363
		return -ENOMEM;
364
	}
Linus Torvalds's avatar
Linus Torvalds committed
365

366 367
	env->envp[env->envp_idx++] = &env->buf[env->buflen];
	env->buflen += len + 1;
Linus Torvalds's avatar
Linus Torvalds committed
368 369
	return 0;
}
370
EXPORT_SYMBOL_GPL(add_uevent_var);
Linus Torvalds's avatar
Linus Torvalds committed
371

372
#if defined(CONFIG_NET)
373
static int uevent_net_init(struct net *net)
374
{
375
	struct uevent_sock *ue_sk;
376 377
	struct netlink_kernel_cfg cfg = {
		.groups	= 1,
378
		.flags	= NL_CFG_F_NONROOT_RECV,
379
	};
380 381 382 383 384

	ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL);
	if (!ue_sk)
		return -ENOMEM;

385
	ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, &cfg);
386
	if (!ue_sk->sk) {
387 388
		printk(KERN_ERR
		       "kobject_uevent: unable to create netlink socket!\n");
389
		kfree(ue_sk);
390 391
		return -ENODEV;
	}
392 393 394
	mutex_lock(&uevent_sock_mutex);
	list_add_tail(&ue_sk->list, &uevent_sock_list);
	mutex_unlock(&uevent_sock_mutex);
395 396 397
	return 0;
}

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
static void uevent_net_exit(struct net *net)
{
	struct uevent_sock *ue_sk;

	mutex_lock(&uevent_sock_mutex);
	list_for_each_entry(ue_sk, &uevent_sock_list, list) {
		if (sock_net(ue_sk->sk) == net)
			goto found;
	}
	mutex_unlock(&uevent_sock_mutex);
	return;

found:
	list_del(&ue_sk->list);
	mutex_unlock(&uevent_sock_mutex);

	netlink_kernel_release(ue_sk->sk);
	kfree(ue_sk);
}

static struct pernet_operations uevent_net_ops = {
	.init	= uevent_net_init,
	.exit	= uevent_net_exit,
};

static int __init kobject_uevent_init(void)
{
	return register_pernet_subsys(&uevent_net_ops);
}


429
postcore_initcall(kobject_uevent_init);
430
#endif