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 };