Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
9f46080c MH |
2 | /* |
3 | * cn_proc.c - process events connector | |
4 | * | |
5 | * Copyright (C) Matt Helsley, IBM Corp. 2005 | |
6 | * Based on cn_fork.c by Guillaume Thouvenin <guillaume.thouvenin@bull.net> | |
7 | * Original copyright notice follows: | |
8 | * Copyright (C) 2005 BULL SA. | |
9f46080c MH |
9 | */ |
10 | ||
9f46080c | 11 | #include <linux/kernel.h> |
caf3c9dc | 12 | #include <linux/ktime.h> |
9f46080c | 13 | #include <linux/init.h> |
1d31a4ea | 14 | #include <linux/connector.h> |
5a0e3ad6 | 15 | #include <linux/gfp.h> |
f701e5b7 | 16 | #include <linux/ptrace.h> |
60063497 | 17 | #include <linux/atomic.h> |
9582d901 | 18 | #include <linux/pid_namespace.h> |
60063497 | 19 | |
9f46080c | 20 | #include <linux/cn_proc.h> |
3e92fd7b | 21 | #include <linux/local_lock.h> |
9f46080c | 22 | |
1ca1a4cf CM |
23 | /* |
24 | * Size of a cn_msg followed by a proc_event structure. Since the | |
25 | * sizeof struct cn_msg is a multiple of 4 bytes, but not 8 bytes, we | |
26 | * add one 4-byte word to the size here, and then start the actual | |
27 | * cn_msg structure 4 bytes into the stack buffer. The result is that | |
28 | * the immediately following proc_event structure is aligned to 8 bytes. | |
29 | */ | |
30 | #define CN_PROC_MSG_SIZE (sizeof(struct cn_msg) + sizeof(struct proc_event) + 4) | |
31 | ||
32 | /* See comment above; we test our assumption about sizeof struct cn_msg here. */ | |
33 | static inline struct cn_msg *buffer_to_cn_msg(__u8 *buffer) | |
34 | { | |
35 | BUILD_BUG_ON(sizeof(struct cn_msg) != 20); | |
36 | return (struct cn_msg *)(buffer + 4); | |
37 | } | |
9f46080c MH |
38 | |
39 | static atomic_t proc_event_num_listeners = ATOMIC_INIT(0); | |
40 | static struct cb_id cn_proc_event_id = { CN_IDX_PROC, CN_VAL_PROC }; | |
41 | ||
3e92fd7b MG |
42 | /* local_event.count is used as the sequence number of the netlink message */ |
43 | struct local_event { | |
44 | local_lock_t lock; | |
45 | __u32 count; | |
46 | }; | |
47 | static DEFINE_PER_CPU(struct local_event, local_event) = { | |
48 | .lock = INIT_LOCAL_LOCK(lock), | |
49 | }; | |
9f46080c | 50 | |
2aa1f7a1 AK |
51 | static int cn_filter(struct sock *dsk, struct sk_buff *skb, void *data) |
52 | { | |
743acf35 | 53 | __u32 what, exit_code, *ptr; |
2aa1f7a1 | 54 | enum proc_cn_mcast_op mc_op; |
743acf35 | 55 | uintptr_t val; |
2aa1f7a1 | 56 | |
9644bc49 | 57 | if (!dsk || !dsk->sk_user_data || !data) |
2aa1f7a1 AK |
58 | return 0; |
59 | ||
743acf35 AK |
60 | ptr = (__u32 *)data; |
61 | what = *ptr++; | |
62 | exit_code = *ptr; | |
63 | val = ((struct proc_input *)(dsk->sk_user_data))->event_type; | |
2aa1f7a1 AK |
64 | mc_op = ((struct proc_input *)(dsk->sk_user_data))->mcast_op; |
65 | ||
66 | if (mc_op == PROC_CN_MCAST_IGNORE) | |
67 | return 1; | |
68 | ||
743acf35 AK |
69 | if ((__u32)val == PROC_EVENT_ALL) |
70 | return 0; | |
71 | ||
72 | /* | |
73 | * Drop packet if we have to report only non-zero exit status | |
74 | * (PROC_EVENT_NONZERO_EXIT) and exit status is 0 | |
75 | */ | |
76 | if (((__u32)val & PROC_EVENT_NONZERO_EXIT) && | |
77 | (what == PROC_EVENT_EXIT)) { | |
78 | if (exit_code) | |
79 | return 0; | |
80 | } | |
81 | ||
82 | if ((__u32)val & what) | |
83 | return 0; | |
84 | ||
85 | return 1; | |
2aa1f7a1 AK |
86 | } |
87 | ||
ab8ed951 | 88 | static inline void send_msg(struct cn_msg *msg) |
9f46080c | 89 | { |
743acf35 AK |
90 | __u32 filter_data[2]; |
91 | ||
3e92fd7b | 92 | local_lock(&local_event.lock); |
ab8ed951 | 93 | |
3e92fd7b | 94 | msg->seq = __this_cpu_inc_return(local_event.count) - 1; |
ab8ed951 AC |
95 | ((struct proc_event *)msg->data)->cpu = smp_processor_id(); |
96 | ||
97 | /* | |
3e92fd7b MG |
98 | * local_lock() disables preemption during send to ensure the messages |
99 | * are ordered according to their sequence numbers. | |
ab8ed951 AC |
100 | * |
101 | * If cn_netlink_send() fails, the data is not sent. | |
102 | */ | |
743acf35 AK |
103 | filter_data[0] = ((struct proc_event *)msg->data)->what; |
104 | if (filter_data[0] == PROC_EVENT_EXIT) { | |
105 | filter_data[1] = | |
106 | ((struct proc_event *)msg->data)->event_data.exit.exit_code; | |
107 | } else { | |
108 | filter_data[1] = 0; | |
109 | } | |
110 | ||
8929f95b KW |
111 | cn_netlink_send_mult(msg, msg->len, 0, CN_IDX_PROC, GFP_NOWAIT, |
112 | cn_filter, (void *)filter_data); | |
ab8ed951 | 113 | |
3e92fd7b | 114 | local_unlock(&local_event.lock); |
9f46080c MH |
115 | } |
116 | ||
117 | void proc_fork_connector(struct task_struct *task) | |
118 | { | |
119 | struct cn_msg *msg; | |
120 | struct proc_event *ev; | |
1ca1a4cf | 121 | __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); |
9e8f90df | 122 | struct task_struct *parent; |
9f46080c MH |
123 | |
124 | if (atomic_read(&proc_event_num_listeners) < 1) | |
125 | return; | |
126 | ||
1ca1a4cf | 127 | msg = buffer_to_cn_msg(buffer); |
f3c48ecc | 128 | ev = (struct proc_event *)msg->data; |
e727ca82 | 129 | memset(&ev->event_data, 0, sizeof(ev->event_data)); |
9e93f21b | 130 | ev->timestamp_ns = ktime_get_ns(); |
9f46080c | 131 | ev->what = PROC_EVENT_FORK; |
9e8f90df ON |
132 | rcu_read_lock(); |
133 | parent = rcu_dereference(task->real_parent); | |
134 | ev->event_data.fork.parent_pid = parent->pid; | |
135 | ev->event_data.fork.parent_tgid = parent->tgid; | |
136 | rcu_read_unlock(); | |
9f46080c MH |
137 | ev->event_data.fork.child_pid = task->pid; |
138 | ev->event_data.fork.child_tgid = task->tgid; | |
139 | ||
140 | memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); | |
141 | msg->ack = 0; /* not used */ | |
142 | msg->len = sizeof(*ev); | |
e727ca82 | 143 | msg->flags = 0; /* not used */ |
ab8ed951 | 144 | send_msg(msg); |
9f46080c MH |
145 | } |
146 | ||
147 | void proc_exec_connector(struct task_struct *task) | |
148 | { | |
149 | struct cn_msg *msg; | |
150 | struct proc_event *ev; | |
1ca1a4cf | 151 | __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); |
9f46080c MH |
152 | |
153 | if (atomic_read(&proc_event_num_listeners) < 1) | |
154 | return; | |
155 | ||
1ca1a4cf | 156 | msg = buffer_to_cn_msg(buffer); |
f3c48ecc | 157 | ev = (struct proc_event *)msg->data; |
e727ca82 | 158 | memset(&ev->event_data, 0, sizeof(ev->event_data)); |
9e93f21b | 159 | ev->timestamp_ns = ktime_get_ns(); |
9f46080c MH |
160 | ev->what = PROC_EVENT_EXEC; |
161 | ev->event_data.exec.process_pid = task->pid; | |
162 | ev->event_data.exec.process_tgid = task->tgid; | |
163 | ||
164 | memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); | |
165 | msg->ack = 0; /* not used */ | |
166 | msg->len = sizeof(*ev); | |
e727ca82 | 167 | msg->flags = 0; /* not used */ |
ab8ed951 | 168 | send_msg(msg); |
9f46080c MH |
169 | } |
170 | ||
171 | void proc_id_connector(struct task_struct *task, int which_id) | |
172 | { | |
173 | struct cn_msg *msg; | |
174 | struct proc_event *ev; | |
1ca1a4cf | 175 | __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); |
c69e8d9c | 176 | const struct cred *cred; |
9f46080c MH |
177 | |
178 | if (atomic_read(&proc_event_num_listeners) < 1) | |
179 | return; | |
180 | ||
1ca1a4cf | 181 | msg = buffer_to_cn_msg(buffer); |
f3c48ecc | 182 | ev = (struct proc_event *)msg->data; |
e727ca82 | 183 | memset(&ev->event_data, 0, sizeof(ev->event_data)); |
9f46080c MH |
184 | ev->what = which_id; |
185 | ev->event_data.id.process_pid = task->pid; | |
186 | ev->event_data.id.process_tgid = task->tgid; | |
c69e8d9c DH |
187 | rcu_read_lock(); |
188 | cred = __task_cred(task); | |
9f46080c | 189 | if (which_id == PROC_EVENT_UID) { |
9582d901 EB |
190 | ev->event_data.id.r.ruid = from_kuid_munged(&init_user_ns, cred->uid); |
191 | ev->event_data.id.e.euid = from_kuid_munged(&init_user_ns, cred->euid); | |
9f46080c | 192 | } else if (which_id == PROC_EVENT_GID) { |
9582d901 EB |
193 | ev->event_data.id.r.rgid = from_kgid_munged(&init_user_ns, cred->gid); |
194 | ev->event_data.id.e.egid = from_kgid_munged(&init_user_ns, cred->egid); | |
c69e8d9c DH |
195 | } else { |
196 | rcu_read_unlock(); | |
f3c48ecc | 197 | return; |
c69e8d9c DH |
198 | } |
199 | rcu_read_unlock(); | |
9e93f21b | 200 | ev->timestamp_ns = ktime_get_ns(); |
9f46080c MH |
201 | |
202 | memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); | |
203 | msg->ack = 0; /* not used */ | |
204 | msg->len = sizeof(*ev); | |
e727ca82 | 205 | msg->flags = 0; /* not used */ |
ab8ed951 | 206 | send_msg(msg); |
9f46080c MH |
207 | } |
208 | ||
02b51df1 SJR |
209 | void proc_sid_connector(struct task_struct *task) |
210 | { | |
211 | struct cn_msg *msg; | |
212 | struct proc_event *ev; | |
1ca1a4cf | 213 | __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); |
02b51df1 SJR |
214 | |
215 | if (atomic_read(&proc_event_num_listeners) < 1) | |
216 | return; | |
217 | ||
1ca1a4cf | 218 | msg = buffer_to_cn_msg(buffer); |
02b51df1 | 219 | ev = (struct proc_event *)msg->data; |
e727ca82 | 220 | memset(&ev->event_data, 0, sizeof(ev->event_data)); |
9e93f21b | 221 | ev->timestamp_ns = ktime_get_ns(); |
02b51df1 SJR |
222 | ev->what = PROC_EVENT_SID; |
223 | ev->event_data.sid.process_pid = task->pid; | |
224 | ev->event_data.sid.process_tgid = task->tgid; | |
225 | ||
226 | memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); | |
227 | msg->ack = 0; /* not used */ | |
228 | msg->len = sizeof(*ev); | |
e727ca82 | 229 | msg->flags = 0; /* not used */ |
ab8ed951 | 230 | send_msg(msg); |
02b51df1 SJR |
231 | } |
232 | ||
f701e5b7 VZ |
233 | void proc_ptrace_connector(struct task_struct *task, int ptrace_id) |
234 | { | |
235 | struct cn_msg *msg; | |
236 | struct proc_event *ev; | |
1ca1a4cf | 237 | __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); |
f701e5b7 VZ |
238 | |
239 | if (atomic_read(&proc_event_num_listeners) < 1) | |
240 | return; | |
241 | ||
1ca1a4cf | 242 | msg = buffer_to_cn_msg(buffer); |
f701e5b7 | 243 | ev = (struct proc_event *)msg->data; |
e727ca82 | 244 | memset(&ev->event_data, 0, sizeof(ev->event_data)); |
9e93f21b | 245 | ev->timestamp_ns = ktime_get_ns(); |
f701e5b7 VZ |
246 | ev->what = PROC_EVENT_PTRACE; |
247 | ev->event_data.ptrace.process_pid = task->pid; | |
248 | ev->event_data.ptrace.process_tgid = task->tgid; | |
249 | if (ptrace_id == PTRACE_ATTACH) { | |
250 | ev->event_data.ptrace.tracer_pid = current->pid; | |
251 | ev->event_data.ptrace.tracer_tgid = current->tgid; | |
252 | } else if (ptrace_id == PTRACE_DETACH) { | |
253 | ev->event_data.ptrace.tracer_pid = 0; | |
254 | ev->event_data.ptrace.tracer_tgid = 0; | |
255 | } else | |
256 | return; | |
257 | ||
258 | memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); | |
259 | msg->ack = 0; /* not used */ | |
260 | msg->len = sizeof(*ev); | |
e727ca82 | 261 | msg->flags = 0; /* not used */ |
ab8ed951 | 262 | send_msg(msg); |
f701e5b7 VZ |
263 | } |
264 | ||
f786ecba VZ |
265 | void proc_comm_connector(struct task_struct *task) |
266 | { | |
267 | struct cn_msg *msg; | |
268 | struct proc_event *ev; | |
1ca1a4cf | 269 | __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); |
f786ecba VZ |
270 | |
271 | if (atomic_read(&proc_event_num_listeners) < 1) | |
272 | return; | |
273 | ||
1ca1a4cf | 274 | msg = buffer_to_cn_msg(buffer); |
f786ecba | 275 | ev = (struct proc_event *)msg->data; |
e727ca82 | 276 | memset(&ev->event_data, 0, sizeof(ev->event_data)); |
9e93f21b | 277 | ev->timestamp_ns = ktime_get_ns(); |
f786ecba VZ |
278 | ev->what = PROC_EVENT_COMM; |
279 | ev->event_data.comm.process_pid = task->pid; | |
280 | ev->event_data.comm.process_tgid = task->tgid; | |
281 | get_task_comm(ev->event_data.comm.comm, task); | |
282 | ||
283 | memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); | |
284 | msg->ack = 0; /* not used */ | |
285 | msg->len = sizeof(*ev); | |
e727ca82 | 286 | msg->flags = 0; /* not used */ |
ab8ed951 | 287 | send_msg(msg); |
2b5faa4c JD |
288 | } |
289 | ||
290 | void proc_coredump_connector(struct task_struct *task) | |
291 | { | |
292 | struct cn_msg *msg; | |
293 | struct proc_event *ev; | |
6d2b0f02 | 294 | struct task_struct *parent; |
1ca1a4cf | 295 | __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); |
2b5faa4c JD |
296 | |
297 | if (atomic_read(&proc_event_num_listeners) < 1) | |
298 | return; | |
299 | ||
1ca1a4cf | 300 | msg = buffer_to_cn_msg(buffer); |
2b5faa4c | 301 | ev = (struct proc_event *)msg->data; |
e727ca82 | 302 | memset(&ev->event_data, 0, sizeof(ev->event_data)); |
9e93f21b | 303 | ev->timestamp_ns = ktime_get_ns(); |
2b5faa4c JD |
304 | ev->what = PROC_EVENT_COREDUMP; |
305 | ev->event_data.coredump.process_pid = task->pid; | |
306 | ev->event_data.coredump.process_tgid = task->tgid; | |
6d2b0f02 LR |
307 | |
308 | rcu_read_lock(); | |
309 | if (pid_alive(task)) { | |
310 | parent = rcu_dereference(task->real_parent); | |
311 | ev->event_data.coredump.parent_pid = parent->pid; | |
312 | ev->event_data.coredump.parent_tgid = parent->tgid; | |
313 | } | |
314 | rcu_read_unlock(); | |
2b5faa4c JD |
315 | |
316 | memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); | |
317 | msg->ack = 0; /* not used */ | |
318 | msg->len = sizeof(*ev); | |
e727ca82 | 319 | msg->flags = 0; /* not used */ |
ab8ed951 | 320 | send_msg(msg); |
f786ecba VZ |
321 | } |
322 | ||
9f46080c MH |
323 | void proc_exit_connector(struct task_struct *task) |
324 | { | |
325 | struct cn_msg *msg; | |
326 | struct proc_event *ev; | |
6d2b0f02 | 327 | struct task_struct *parent; |
1ca1a4cf | 328 | __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); |
9f46080c MH |
329 | |
330 | if (atomic_read(&proc_event_num_listeners) < 1) | |
331 | return; | |
332 | ||
1ca1a4cf | 333 | msg = buffer_to_cn_msg(buffer); |
f3c48ecc | 334 | ev = (struct proc_event *)msg->data; |
e727ca82 | 335 | memset(&ev->event_data, 0, sizeof(ev->event_data)); |
9e93f21b | 336 | ev->timestamp_ns = ktime_get_ns(); |
9f46080c MH |
337 | ev->what = PROC_EVENT_EXIT; |
338 | ev->event_data.exit.process_pid = task->pid; | |
339 | ev->event_data.exit.process_tgid = task->tgid; | |
340 | ev->event_data.exit.exit_code = task->exit_code; | |
341 | ev->event_data.exit.exit_signal = task->exit_signal; | |
6d2b0f02 LR |
342 | |
343 | rcu_read_lock(); | |
344 | if (pid_alive(task)) { | |
345 | parent = rcu_dereference(task->real_parent); | |
346 | ev->event_data.exit.parent_pid = parent->pid; | |
347 | ev->event_data.exit.parent_tgid = parent->tgid; | |
348 | } | |
349 | rcu_read_unlock(); | |
9f46080c MH |
350 | |
351 | memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); | |
352 | msg->ack = 0; /* not used */ | |
353 | msg->len = sizeof(*ev); | |
e727ca82 | 354 | msg->flags = 0; /* not used */ |
ab8ed951 | 355 | send_msg(msg); |
9f46080c MH |
356 | } |
357 | ||
358 | /* | |
359 | * Send an acknowledgement message to userspace | |
360 | * | |
361 | * Use 0 for success, EFOO otherwise. | |
362 | * Note: this is the negative of conventional kernel error | |
363 | * values because it's not being returned via syscall return | |
364 | * mechanisms. | |
365 | */ | |
366 | static void cn_proc_ack(int err, int rcvd_seq, int rcvd_ack) | |
367 | { | |
368 | struct cn_msg *msg; | |
369 | struct proc_event *ev; | |
1ca1a4cf | 370 | __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); |
9f46080c MH |
371 | |
372 | if (atomic_read(&proc_event_num_listeners) < 1) | |
373 | return; | |
374 | ||
1ca1a4cf | 375 | msg = buffer_to_cn_msg(buffer); |
f3c48ecc | 376 | ev = (struct proc_event *)msg->data; |
e727ca82 | 377 | memset(&ev->event_data, 0, sizeof(ev->event_data)); |
9f46080c | 378 | msg->seq = rcvd_seq; |
9e93f21b | 379 | ev->timestamp_ns = ktime_get_ns(); |
9f46080c MH |
380 | ev->cpu = -1; |
381 | ev->what = PROC_EVENT_NONE; | |
382 | ev->event_data.ack.err = err; | |
383 | memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); | |
384 | msg->ack = rcvd_ack + 1; | |
385 | msg->len = sizeof(*ev); | |
e727ca82 | 386 | msg->flags = 0; /* not used */ |
ab8ed951 | 387 | send_msg(msg); |
9f46080c MH |
388 | } |
389 | ||
390 | /** | |
391 | * cn_proc_mcast_ctl | |
743acf35 AK |
392 | * @msg: message sent from userspace via the connector |
393 | * @nsp: NETLINK_CB of the client's socket buffer | |
9f46080c | 394 | */ |
f0b25932 SB |
395 | static void cn_proc_mcast_ctl(struct cn_msg *msg, |
396 | struct netlink_skb_parms *nsp) | |
9f46080c | 397 | { |
2aa1f7a1 | 398 | enum proc_cn_mcast_op mc_op = 0, prev_mc_op = 0; |
743acf35 AK |
399 | struct proc_input *pinput = NULL; |
400 | enum proc_cn_event ev_type = 0; | |
2aa1f7a1 AK |
401 | int err = 0, initial = 0; |
402 | struct sock *sk = NULL; | |
9f46080c | 403 | |
9582d901 EB |
404 | /* |
405 | * Events are reported with respect to the initial pid | |
406 | * and user namespaces so ignore requestors from | |
407 | * other namespaces. | |
408 | */ | |
409 | if ((current_user_ns() != &init_user_ns) || | |
42c66d16 | 410 | !task_is_in_init_pid_ns(current)) |
9582d901 EB |
411 | return; |
412 | ||
743acf35 AK |
413 | if (msg->len == sizeof(*pinput)) { |
414 | pinput = (struct proc_input *)msg->data; | |
415 | mc_op = pinput->mcast_op; | |
416 | ev_type = pinput->event_type; | |
417 | } else if (msg->len == sizeof(mc_op)) { | |
2aa1f7a1 | 418 | mc_op = *((enum proc_cn_mcast_op *)msg->data); |
743acf35 AK |
419 | ev_type = PROC_EVENT_ALL; |
420 | } else { | |
2aa1f7a1 | 421 | return; |
743acf35 AK |
422 | } |
423 | ||
424 | ev_type = valid_event((enum proc_cn_event)ev_type); | |
425 | ||
426 | if (ev_type == PROC_EVENT_NONE) | |
427 | ev_type = PROC_EVENT_ALL; | |
2aa1f7a1 AK |
428 | |
429 | if (nsp->sk) { | |
430 | sk = nsp->sk; | |
431 | if (sk->sk_user_data == NULL) { | |
432 | sk->sk_user_data = kzalloc(sizeof(struct proc_input), | |
433 | GFP_KERNEL); | |
434 | if (sk->sk_user_data == NULL) { | |
435 | err = ENOMEM; | |
436 | goto out; | |
437 | } | |
438 | initial = 1; | |
439 | } else { | |
440 | prev_mc_op = | |
441 | ((struct proc_input *)(sk->sk_user_data))->mcast_op; | |
442 | } | |
743acf35 AK |
443 | ((struct proc_input *)(sk->sk_user_data))->event_type = |
444 | ev_type; | |
2aa1f7a1 AK |
445 | ((struct proc_input *)(sk->sk_user_data))->mcast_op = mc_op; |
446 | } | |
447 | ||
448 | switch (mc_op) { | |
9f46080c | 449 | case PROC_CN_MCAST_LISTEN: |
2aa1f7a1 AK |
450 | if (initial || (prev_mc_op != PROC_CN_MCAST_LISTEN)) |
451 | atomic_inc(&proc_event_num_listeners); | |
9f46080c MH |
452 | break; |
453 | case PROC_CN_MCAST_IGNORE: | |
2aa1f7a1 AK |
454 | if (!initial && (prev_mc_op != PROC_CN_MCAST_IGNORE)) |
455 | atomic_dec(&proc_event_num_listeners); | |
743acf35 AK |
456 | ((struct proc_input *)(sk->sk_user_data))->event_type = |
457 | PROC_EVENT_NONE; | |
9f46080c MH |
458 | break; |
459 | default: | |
460 | err = EINVAL; | |
461 | break; | |
462 | } | |
e70ab977 KC |
463 | |
464 | out: | |
9f46080c MH |
465 | cn_proc_ack(err, msg->seq, msg->ack); |
466 | } | |
467 | ||
468 | /* | |
469 | * cn_proc_init - initialization entry point | |
470 | * | |
471 | * Adds the connector callback to the connector driver. | |
472 | */ | |
473 | static int __init cn_proc_init(void) | |
474 | { | |
f3c48ecc VI |
475 | int err = cn_add_callback(&cn_proc_event_id, |
476 | "cn_proc", | |
477 | &cn_proc_mcast_ctl); | |
478 | if (err) { | |
479 | pr_warn("cn_proc failed to register\n"); | |
9f46080c MH |
480 | return err; |
481 | } | |
482 | return 0; | |
483 | } | |
8297f2d9 | 484 | device_initcall(cn_proc_init); |