xref: /illumos-gate/usr/src/uts/common/fs/ctfs/ctfs_event.c (revision dd72704bd9e794056c558153663c739e2012d721)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/types.h>
27 #include <sys/param.h>
28 #include <sys/time.h>
29 #include <sys/cred.h>
30 #include <sys/vfs.h>
31 #include <sys/vfs_opreg.h>
32 #include <sys/gfs.h>
33 #include <sys/vnode.h>
34 #include <sys/systm.h>
35 #include <sys/errno.h>
36 #include <sys/sysmacros.h>
37 #include <fs/fs_subr.h>
38 #include <sys/contract.h>
39 #include <sys/contract_impl.h>
40 #include <sys/ctfs.h>
41 #include <sys/ctfs_impl.h>
42 #include <sys/file.h>
43 #include <sys/policy.h>
44 
45 /*
46  * CTFS routines for the /system/contract/<type>/bundle vnode.
47  * CTFS routines for the /system/contract/<type>/pbundle vnode.
48  * CTFS routines for the /system/contract/<type>/<ctid>/events vnode.
49  */
50 
51 /*
52  * ctfs_endpoint_open
53  *
54  * Called by the VOP_OPEN entry points to perform some common checks
55  * and set up the endpoint listener, if not already done.
56  */
57 static int
58 ctfs_endpoint_open(ctfs_endpoint_t *endpt, ct_equeue_t *q, int flag)
59 {
60 	if ((flag & ~FNONBLOCK) != (FREAD | FOFFMAX))
61 		return (EINVAL);
62 
63 	mutex_enter(&endpt->ctfs_endpt_lock);
64 	if ((endpt->ctfs_endpt_flags & CTFS_ENDPT_SETUP) == 0) {
65 		endpt->ctfs_endpt_flags |= CTFS_ENDPT_SETUP;
66 		if (flag & FNONBLOCK)
67 			endpt->ctfs_endpt_flags |= CTFS_ENDPT_NBLOCK;
68 		cte_add_listener(q, &endpt->ctfs_endpt_listener);
69 	}
70 	mutex_exit(&endpt->ctfs_endpt_lock);
71 
72 	return (0);
73 }
74 
75 /*
76  * ctfs_endpoint inactive
77  *
78  * Called by the VOP_INACTIVE entry points to perform common listener
79  * cleanup.
80  */
81 static void
82 ctfs_endpoint_inactive(ctfs_endpoint_t *endpt)
83 {
84 	mutex_enter(&endpt->ctfs_endpt_lock);
85 	if (endpt->ctfs_endpt_flags & CTFS_ENDPT_SETUP) {
86 		endpt->ctfs_endpt_flags = 0;
87 		cte_remove_listener(&endpt->ctfs_endpt_listener);
88 	}
89 	pollhead_clean(&endpt->ctfs_endpt_listener.ctl_pollhead);
90 	mutex_exit(&endpt->ctfs_endpt_lock);
91 }
92 
93 /*
94  * ctfs_endpoint_ioctl
95  *
96  * Implements the common VOP_IOCTL handling for the event endpoints.
97  * rprivchk, if true, indicates that event receive requests should
98  * check the provided credentials.  This distinction exists because
99  * contract endpoints perform their privilege checks at open-time, and
100  * process bundle queue listeners by definition may view all events
101  * their queues contain.
102  */
103 static int
104 ctfs_endpoint_ioctl(ctfs_endpoint_t *endpt, int cmd, intptr_t arg, cred_t *cr,
105     zone_t *zone, int rprivchk)
106 {
107 	uint64_t id, zuniqid;
108 
109 	zuniqid = zone->zone_uniqid;
110 
111 	switch (cmd) {
112 	case CT_ERESET:
113 		cte_reset_listener(&endpt->ctfs_endpt_listener);
114 		break;
115 	case CT_ERECV:
116 		/*
117 		 * We pass in NULL for the cred when reading from
118 		 * process bundle queues and contract queues because
119 		 * the privilege check was performed at open time.
120 		 */
121 		return (cte_get_event(&endpt->ctfs_endpt_listener,
122 		    endpt->ctfs_endpt_flags & CTFS_ENDPT_NBLOCK,
123 		    (void *)arg, rprivchk ? cr : NULL, zuniqid, 0));
124 	case CT_ECRECV:
125 		return (cte_get_event(&endpt->ctfs_endpt_listener,
126 		    endpt->ctfs_endpt_flags & CTFS_ENDPT_NBLOCK,
127 		    (void *)arg, rprivchk ? cr : NULL, zuniqid, 1));
128 	case CT_ENEXT:
129 		if (copyin((void *)arg, &id, sizeof (uint64_t)))
130 			return (EFAULT);
131 		return (cte_next_event(&endpt->ctfs_endpt_listener, id));
132 	case CT_ERELIABLE:
133 		return (cte_set_reliable(&endpt->ctfs_endpt_listener, cr));
134 	default:
135 		return (EINVAL);
136 	}
137 
138 	return (0);
139 }
140 
141 /*
142  * ctfs_endpoint_poll
143  *
144  * Called by the VOP_POLL entry points.
145  */
146 static int
147 ctfs_endpoint_poll(ctfs_endpoint_t *endpt, short events, int anyyet,
148     short *reventsp, pollhead_t **php)
149 {
150 	if ((events & POLLIN) && endpt->ctfs_endpt_listener.ctl_position) {
151 		*reventsp = POLLIN;
152 	} else {
153 		*reventsp = 0;
154 		if (!anyyet)
155 			*php = &endpt->ctfs_endpt_listener.ctl_pollhead;
156 	}
157 
158 	return (0);
159 }
160 
161 /*
162  * ctfs_create_evnode
163  *
164  * Creates and returns a new evnode.
165  */
166 vnode_t *
167 ctfs_create_evnode(vnode_t *pvp)
168 {
169 	vnode_t *vp;
170 	ctfs_evnode_t *evnode;
171 	ctfs_cdirnode_t *cdirnode = pvp->v_data;
172 
173 	vp = gfs_file_create(sizeof (ctfs_evnode_t), pvp, ctfs_ops_event);
174 	evnode = vp->v_data;
175 
176 	/*
177 	 * We transitively have a hold on the contract through our
178 	 * parent directory.
179 	 */
180 	evnode->ctfs_ev_contract = cdirnode->ctfs_cn_contract;
181 
182 	return (vp);
183 }
184 
185 /*
186  * ctfs_ev_access - VOP_ACCESS entry point
187  *
188  * You only get to access event files for contracts you or your
189  * effective user id owns, unless you have a privilege.
190  */
191 /*ARGSUSED*/
192 static int
193 ctfs_ev_access(
194 	vnode_t *vp,
195 	int mode,
196 	int flags,
197 	cred_t *cr,
198 	caller_context_t *cct)
199 {
200 	ctfs_evnode_t *evnode = vp->v_data;
201 	contract_t *ct = evnode->ctfs_ev_contract;
202 	int error;
203 
204 	if (mode & (VWRITE | VEXEC))
205 		return (EACCES);
206 
207 	if (error = secpolicy_contract_observer(cr, ct))
208 		return (error);
209 
210 	return (0);
211 }
212 
213 /*
214  * ctfs_ev_open - VOP_OPEN entry point
215  *
216  * Performs the same privilege checks as ctfs_ev_access, and then calls
217  * ctfs_endpoint_open to perform the common endpoint initialization.
218  */
219 /* ARGSUSED */
220 static int
221 ctfs_ev_open(vnode_t **vpp, int flag, cred_t *cr, caller_context_t *cct)
222 {
223 	ctfs_evnode_t *evnode = (*vpp)->v_data;
224 	contract_t *ct = evnode->ctfs_ev_contract;
225 	int error;
226 
227 	if (error = secpolicy_contract_observer(cr, ct))
228 		return (error);
229 
230 	/*
231 	 * See comment in ctfs_bu_open.
232 	 */
233 	return (ctfs_endpoint_open(&evnode->ctfs_ev_listener,
234 	    &evnode->ctfs_ev_contract->ct_events, flag));
235 }
236 
237 /*
238  * ctfs_ev_inactive - VOP_INACTIVE entry point
239  */
240 /* ARGSUSED */
241 static void
242 ctfs_ev_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct)
243 {
244 	ctfs_evnode_t *evnode;
245 	vnode_t *pvp = gfs_file_parent(vp);
246 
247 	/*
248 	 * We must destroy the endpoint before releasing the parent; otherwise
249 	 * we will try to destroy a contract with active listeners.  To prevent
250 	 * this, we grab an extra hold on the parent.
251 	 */
252 	VN_HOLD(pvp);
253 	if ((evnode = gfs_file_inactive(vp)) != NULL) {
254 		ctfs_endpoint_inactive(&evnode->ctfs_ev_listener);
255 		kmem_free(evnode, sizeof (ctfs_evnode_t));
256 	}
257 	VN_RELE(pvp);
258 }
259 
260 /*
261  * ctfs_ev_getattr - VOP_GETATTR entry point
262  */
263 /* ARGSUSED */
264 static int
265 ctfs_ev_getattr(
266 	vnode_t *vp,
267 	vattr_t *vap,
268 	int flags,
269 	cred_t *cr,
270 	caller_context_t *ct)
271 {
272 	ctfs_evnode_t *evnode = vp->v_data;
273 
274 	vap->va_type = VREG;
275 	vap->va_mode = 0444;
276 	vap->va_nlink = 1;
277 	vap->va_size = 0;
278 	vap->va_ctime = evnode->ctfs_ev_contract->ct_ctime;
279 	mutex_enter(&evnode->ctfs_ev_contract->ct_events.ctq_lock);
280 	vap->va_atime = vap->va_mtime =
281 	    evnode->ctfs_ev_contract->ct_events.ctq_atime;
282 	mutex_exit(&evnode->ctfs_ev_contract->ct_events.ctq_lock);
283 	ctfs_common_getattr(vp, vap);
284 
285 	return (0);
286 }
287 
288 /*
289  * ctfs_ev_ioctl - VOP_IOCTL entry point
290  */
291 /* ARGSUSED */
292 static int
293 ctfs_ev_ioctl(
294 	vnode_t *vp,
295 	int cmd,
296 	intptr_t arg,
297 	int flag,
298 	cred_t *cr,
299 	int *rvalp,
300 	caller_context_t *ct)
301 {
302 	ctfs_evnode_t *evnode = vp->v_data;
303 
304 	return (ctfs_endpoint_ioctl(&evnode->ctfs_ev_listener, cmd, arg, cr,
305 	    VTOZONE(vp), 0));
306 }
307 
308 /*
309  * ctfs_ev_poll - VOP_POLL entry point
310  */
311 /*ARGSUSED*/
312 static int
313 ctfs_ev_poll(
314 	vnode_t *vp,
315 	short events,
316 	int anyyet,
317 	short *reventsp,
318 	pollhead_t **php,
319 	caller_context_t *ct)
320 {
321 	ctfs_evnode_t *evnode = vp->v_data;
322 
323 	return (ctfs_endpoint_poll(&evnode->ctfs_ev_listener, events, anyyet,
324 	    reventsp, php));
325 }
326 
327 const fs_operation_def_t ctfs_tops_event[] = {
328 	{ VOPNAME_OPEN,		{ .vop_open = ctfs_ev_open } },
329 	{ VOPNAME_CLOSE,	{ .vop_close = ctfs_close } },
330 	{ VOPNAME_IOCTL,	{ .vop_ioctl = ctfs_ev_ioctl } },
331 	{ VOPNAME_GETATTR,	{ .vop_getattr = ctfs_ev_getattr } },
332 	{ VOPNAME_ACCESS,	{ .vop_access = ctfs_ev_access } },
333 	{ VOPNAME_READDIR,	{ .error = fs_notdir } },
334 	{ VOPNAME_LOOKUP,	{ .error = fs_notdir } },
335 	{ VOPNAME_INACTIVE,	{ .vop_inactive = ctfs_ev_inactive } },
336 	{ VOPNAME_POLL,		{ .vop_poll = ctfs_ev_poll } },
337 	{ NULL, NULL }
338 };
339 
340 /*
341  * ctfs_create_pbundle
342  *
343  * Creates and returns a bunode for a /system/contract/<type>/pbundle
344  * file.
345  */
346 vnode_t *
347 ctfs_create_pbundle(vnode_t *pvp)
348 {
349 	vnode_t *vp;
350 	ctfs_bunode_t *bundle;
351 
352 	vp = gfs_file_create(sizeof (ctfs_bunode_t), pvp, ctfs_ops_bundle);
353 	bundle = vp->v_data;
354 	bundle->ctfs_bu_queue =
355 	    contract_type_pbundle(ct_types[gfs_file_index(pvp)], curproc);
356 
357 	return (vp);
358 }
359 
360 /*
361  * ctfs_create_bundle
362  *
363  * Creates and returns a bunode for a /system/contract/<type>/bundle
364  * file.
365  */
366 vnode_t *
367 ctfs_create_bundle(vnode_t *pvp)
368 {
369 	vnode_t *vp;
370 	ctfs_bunode_t *bundle;
371 
372 	vp = gfs_file_create(sizeof (ctfs_bunode_t), pvp, ctfs_ops_bundle);
373 	bundle = vp->v_data;
374 	bundle->ctfs_bu_queue =
375 	    contract_type_bundle(ct_types[gfs_file_index(pvp)]);
376 
377 	return (vp);
378 }
379 
380 /*
381  * ctfs_bu_open - VOP_OPEN entry point
382  */
383 /* ARGSUSED */
384 static int
385 ctfs_bu_open(vnode_t **vpp, int flag, cred_t *cr, caller_context_t *ct)
386 {
387 	ctfs_bunode_t *bunode = (*vpp)->v_data;
388 
389 	/*
390 	 * This assumes we are only ever called immediately after a
391 	 * VOP_LOOKUP.  We could clone ourselves here, but doing so
392 	 * would make /proc/pid/fd accesses less useful.
393 	 */
394 	return (ctfs_endpoint_open(&bunode->ctfs_bu_listener,
395 	    bunode->ctfs_bu_queue, flag));
396 }
397 
398 /*
399  * ctfs_bu_inactive - VOP_INACTIVE entry point
400  */
401 /* ARGSUSED */
402 static void
403 ctfs_bu_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct)
404 {
405 	ctfs_bunode_t *bunode;
406 	vnode_t *pvp = gfs_file_parent(vp);
407 
408 	/*
409 	 * See comments in ctfs_ev_inactive() above.
410 	 */
411 	VN_HOLD(pvp);
412 	if ((bunode = gfs_file_inactive(vp)) != NULL) {
413 		ctfs_endpoint_inactive(&bunode->ctfs_bu_listener);
414 		kmem_free(bunode, sizeof (ctfs_bunode_t));
415 	}
416 	VN_RELE(pvp);
417 }
418 
419 /*
420  * ctfs_bu_getattr - VOP_GETATTR entry point
421  */
422 /* ARGSUSED */
423 static int
424 ctfs_bu_getattr(
425 	vnode_t *vp,
426 	vattr_t *vap,
427 	int flags,
428 	cred_t *cr,
429 	caller_context_t *ct)
430 {
431 	ctfs_bunode_t *bunode = vp->v_data;
432 
433 	vap->va_type = VREG;
434 	vap->va_mode = 0444;
435 	vap->va_nodeid = gfs_file_index(vp);
436 	vap->va_nlink = 1;
437 	vap->va_size = 0;
438 	vap->va_ctime.tv_sec = vp->v_vfsp->vfs_mtime;
439 	vap->va_ctime.tv_nsec = 0;
440 	mutex_enter(&bunode->ctfs_bu_queue->ctq_lock);
441 	vap->va_mtime = vap->va_atime = bunode->ctfs_bu_queue->ctq_atime;
442 	mutex_exit(&bunode->ctfs_bu_queue->ctq_lock);
443 	ctfs_common_getattr(vp, vap);
444 
445 	return (0);
446 }
447 
448 /*
449  * ctfs_bu_ioctl - VOP_IOCTL entry point
450  */
451 /* ARGSUSED */
452 static int
453 ctfs_bu_ioctl(
454 	vnode_t *vp,
455 	int cmd,
456 	intptr_t arg,
457 	int flag,
458 	cred_t *cr,
459 	int *rvalp,
460 	caller_context_t *ct)
461 {
462 	ctfs_bunode_t *bunode = vp->v_data;
463 
464 	return (ctfs_endpoint_ioctl(&bunode->ctfs_bu_listener, cmd, arg, cr,
465 	    VTOZONE(vp), bunode->ctfs_bu_queue->ctq_listno == CTEL_BUNDLE));
466 }
467 
468 /*
469  * ctfs_bu_poll - VOP_POLL entry point
470  */
471 /*ARGSUSED*/
472 static int
473 ctfs_bu_poll(
474 	vnode_t *vp,
475 	short events,
476 	int anyyet,
477 	short *reventsp,
478 	pollhead_t **php,
479 	caller_context_t *ct)
480 {
481 	ctfs_bunode_t *bunode = vp->v_data;
482 
483 	return (ctfs_endpoint_poll(&bunode->ctfs_bu_listener, events, anyyet,
484 	    reventsp, php));
485 }
486 
487 const fs_operation_def_t ctfs_tops_bundle[] = {
488 	{ VOPNAME_OPEN,		{ .vop_open = ctfs_bu_open } },
489 	{ VOPNAME_CLOSE,	{ .vop_close = ctfs_close } },
490 	{ VOPNAME_IOCTL,	{ .vop_ioctl = ctfs_bu_ioctl } },
491 	{ VOPNAME_GETATTR,	{ .vop_getattr = ctfs_bu_getattr } },
492 	{ VOPNAME_ACCESS,	{ .vop_access = ctfs_access_readonly } },
493 	{ VOPNAME_READDIR,	{ .error = fs_notdir } },
494 	{ VOPNAME_LOOKUP,	{ .error = fs_notdir } },
495 	{ VOPNAME_INACTIVE,	{ .vop_inactive = ctfs_bu_inactive } },
496 	{ VOPNAME_POLL,		{ .vop_poll = ctfs_bu_poll } },
497 	{ NULL, NULL }
498 };
499