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 */
59static int
60ctfs_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 */
83static void
84ctfs_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 */
104static int
105ctfs_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 */
147static int
148ctfs_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 */
167vnode_t *
168ctfs_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*/
193static int
194ctfs_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 */
221static int
222ctfs_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 */
242static void
243ctfs_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 */
265static int
266ctfs_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 */
293static int
294ctfs_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*/
313static int
314ctfs_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
328const 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 */
347vnode_t *
348ctfs_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 */
367vnode_t *
368ctfs_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 */
385static int
386ctfs_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 */
403static void
404ctfs_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 */
424static int
425ctfs_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 */
453static int
454ctfs_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*/
473static int
474ctfs_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
488const 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