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