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 /*
  23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 
  28 /*
  29  * workstation console redirecting driver
  30  *
  31  * Redirects all I/O through a given device instance to the device designated
  32  * as the current target, as given by the vnode associated with the first
  33  * entry in the list of redirections for the given device instance.  The
  34  * implementation assumes that this vnode denotes a STREAMS device; this is
  35  * perhaps a bug.
  36  *
  37  * Supports the SRIOCSREDIR ioctl for designating a new redirection target.
  38  * The new target is added to the front of a list of potentially active
  39  * designees.  Should the device at the front of this list be closed, the new
  40  * front entry assumes active duty.  (Stated differently, redirection targets
  41  * stack, except that it's possible for entries in the interior of the stack
  42  * to go away.)
  43  *
  44  * Supports the SRIOCISREDIR ioctl for inquiring whether the descriptor given
  45  * as argument is the current front of the redirection list associated with
  46  * the descriptor on which the ioctl was issued.
  47  */
  48 
  49 #include <sys/types.h>
  50 #include <sys/sysmacros.h>
  51 #include <sys/open.h>
  52 #include <sys/param.h>
  53 #include <sys/systm.h>
  54 #include <sys/signal.h>
  55 #include <sys/cred.h>
  56 #include <sys/user.h>
  57 #include <sys/proc.h>
  58 #include <sys/vnode.h>
  59 #include <sys/uio.h>
  60 #include <sys/file.h>
  61 #include <sys/kmem.h>
  62 #include <sys/stat.h>
  63 #include <sys/stream.h>
  64 #include <sys/stropts.h>
  65 #include <sys/strsubr.h>
  66 #include <sys/poll.h>
  67 #include <sys/debug.h>
  68 #include <sys/strredir.h>
  69 #include <sys/conf.h>
  70 #include <sys/ddi.h>
  71 #include <sys/sunddi.h>
  72 #include <sys/errno.h>
  73 #include <sys/modctl.h>
  74 #include <sys/sunldi.h>
  75 #include <sys/consdev.h>
  76 #include <sys/fs/snode.h>
  77 
  78 /*
  79  * Global data
  80  */
  81 static dev_info_t       *iwscn_dip;
  82 
  83 /*
  84  * We record the list of redirections as a linked list of iwscn_list_t
  85  * structures.  We need to keep track of the target's vp, so that
  86  * we can vector reads, writes, etc. off to the current designee.
  87  */
  88 typedef struct _iwscn_list {
  89         struct _iwscn_list      *wl_next;       /* next entry */
  90         vnode_t                 *wl_vp;         /* target's vnode */
  91         int                     wl_ref_cnt;     /* operation in progress */
  92         boolean_t               wl_is_console;  /* is the real console */
  93 } iwscn_list_t;
  94 static iwscn_list_t     *iwscn_list;
  95 
  96 /*
  97  * iwscn_list_lock serializes modifications to the global iwscn_list list.
  98  *
  99  * iwscn_list_cv is used when freeing an entry from iwscn_list to allow
 100  * the caller to wait till the wl_ref_cnt field is zero.
 101  *
 102  * iwscn_redirect_lock is used to serialize redirection requests.  This
 103  * is required to ensure that all active redirection streams have
 104  * the redirection streams module (redirmod) pushed on them.
 105  *
 106  * If both iwscn_redirect_lock and iwscn_list_lock must be held then
 107  * iwscn_redirect_lock must be acquired first.
 108  */
 109 static kcondvar_t       iwscn_list_cv;
 110 static kmutex_t         iwscn_list_lock;
 111 static kmutex_t         iwscn_redirect_lock;
 112 
 113 /*
 114  * Routines for managing iwscn_list
 115  */
 116 static vnode_t *
 117 str_vp(vnode_t *vp)
 118 {
 119         /*
 120          * Here we switch to using the vnode that is linked
 121          * to from the stream queue.  (In the case of device
 122          * streams this will correspond to the common vnode
 123          * for the device.)  The reason we use this vnode
 124          * is that when wcmclose() calls srpop(), this is the
 125          * only vnode that it has access to.
 126          */
 127         ASSERT(vp->v_stream != NULL);
 128         return (vp->v_stream->sd_vnode);
 129 }
 130 
 131 /*
 132  * Interrupt any operations that may be outstanding against this vnode.
 133  * optionally, wait for them to complete.
 134  */
 135 static void
 136 srinterrupt(iwscn_list_t *lp, boolean_t wait)
 137 {
 138         ASSERT(MUTEX_HELD(&iwscn_list_lock));
 139 
 140         while (lp->wl_ref_cnt != 0) {
 141                 strsetrerror(lp->wl_vp, EINTR, 0, NULL);
 142                 strsetwerror(lp->wl_vp, EINTR, 0, NULL);
 143                 if (!wait)
 144                         break;
 145                 cv_wait(&iwscn_list_cv, &iwscn_list_lock);
 146         }
 147 }
 148 
 149 /*
 150  * Remove vp from the redirection list rooted at iwscn_list, should it
 151  * be there. Return a pointer to the removed entry.
 152  */
 153 static iwscn_list_t *
 154 srrm(vnode_t *vp)
 155 {
 156         iwscn_list_t    *lp, **lpp;
 157 
 158         ASSERT(MUTEX_HELD(&iwscn_list_lock));
 159 
 160         /* Get the stream vnode */
 161         vp = str_vp(vp);
 162         ASSERT(vp);
 163 
 164         /* Look for this vnode on the redirection list */
 165         for (lpp = &iwscn_list; (lp = *lpp) != NULL; lpp = &lp->wl_next) {
 166                 if (lp->wl_vp == vp)
 167                         break;
 168         }
 169         if (lp != NULL)
 170                 /* Found it, remove this entry from the redirection list */
 171                 *lpp = lp->wl_next;
 172 
 173         return (lp);
 174 }
 175 
 176 /*
 177  * Push vp onto the redirection list.
 178  * If it's already there move it to the front position.
 179  */
 180 static void
 181 srpush(vnode_t *vp, boolean_t is_console)
 182 {
 183         iwscn_list_t    *lp;
 184 
 185         ASSERT(MUTEX_HELD(&iwscn_list_lock));
 186 
 187         /* Get the stream vnode */
 188         vp = str_vp(vp);
 189         ASSERT(vp);
 190 
 191         /* Check if it's already on the redirection list */
 192         if ((lp = srrm(vp)) == NULL) {
 193                 lp = kmem_zalloc(sizeof (*lp), KM_SLEEP);
 194                 lp->wl_vp = vp;
 195                 lp->wl_is_console = is_console;
 196         }
 197         /*
 198          * Note that if this vnode was already somewhere on the redirection
 199          * list then we removed it above and are now bumping it up to the
 200          * front of the redirection list.
 201          */
 202         lp->wl_next = iwscn_list;
 203         iwscn_list = lp;
 204 }
 205 
 206 /*
 207  * This vnode is no longer a valid redirection target. Terminate any current
 208  * operations. If closing, wait for them to complete, then free the entry.
 209  * If called because a hangup has occurred, just deprecate the entry to ensure
 210  * it won't become the target again.
 211  */
 212 void
 213 srpop(vnode_t *vp, boolean_t close)
 214 {
 215         iwscn_list_t    *tlp;           /* This target's entry */
 216         iwscn_list_t    *lp, **lpp;
 217 
 218         mutex_enter(&iwscn_list_lock);
 219 
 220         /*
 221          * Ensure no further operations are directed at the target
 222          * by removing it from the redirection list.
 223          */
 224         if ((tlp = srrm(vp)) == NULL) {
 225                 /* vnode wasn't in the list */
 226                 mutex_exit(&iwscn_list_lock);
 227                 return;
 228         }
 229         /*
 230          * Terminate any current operations.
 231          * If we're closing, wait until they complete.
 232          */
 233         srinterrupt(tlp, close);
 234 
 235         if (close) {
 236                 /* We're finished with this target */
 237                 kmem_free(tlp, sizeof (*tlp));
 238         } else {
 239                 /*
 240                  * Deprecate the entry. There's no need for a flag to indicate
 241                  * this state, it just needs to be moved to the back of the list
 242                  * behind the underlying console device. Since the underlying
 243                  * device anchors the list and is never removed, this entry can
 244                  * never return to the front again to become the target.
 245                  */
 246                 for (lpp = &iwscn_list; (lp = *lpp) != NULL; )
 247                         lpp = &lp->wl_next;
 248                 tlp->wl_next = NULL;
 249                 *lpp = tlp;
 250         }
 251         mutex_exit(&iwscn_list_lock);
 252 }
 253 
 254 /* Get a hold on the current target */
 255 static iwscn_list_t *
 256 srhold()
 257 {
 258         iwscn_list_t    *lp;
 259 
 260         mutex_enter(&iwscn_list_lock);
 261         ASSERT(iwscn_list != NULL);
 262         lp = iwscn_list;
 263         ASSERT(lp->wl_ref_cnt >= 0);
 264         lp->wl_ref_cnt++;
 265         mutex_exit(&iwscn_list_lock);
 266 
 267         return (lp);
 268 }
 269 
 270 /* Release a hold on an entry from the redirection list */
 271 static void
 272 srrele(iwscn_list_t *lp)
 273 {
 274         ASSERT(lp != NULL);
 275         mutex_enter(&iwscn_list_lock);
 276         ASSERT(lp->wl_ref_cnt > 0);
 277         lp->wl_ref_cnt--;
 278         cv_broadcast(&iwscn_list_cv);
 279         mutex_exit(&iwscn_list_lock);
 280 }
 281 
 282 static int
 283 iwscnread(dev_t dev, uio_t *uio, cred_t *cred)
 284 {
 285         iwscn_list_t    *lp;
 286         int             error;
 287 
 288         ASSERT(getminor(dev) == 0);
 289 
 290         lp = srhold();
 291         error = strread(lp->wl_vp, uio, cred);
 292         srrele(lp);
 293 
 294         return (error);
 295 }
 296 
 297 static int
 298 iwscnwrite(dev_t dev, uio_t *uio, cred_t *cred)
 299 {
 300         iwscn_list_t    *lp;
 301         int             error;
 302 
 303         ASSERT(getminor(dev) == 0);
 304 
 305         lp = srhold();
 306         error = strwrite(lp->wl_vp, uio, cred);
 307         srrele(lp);
 308 
 309         return (error);
 310 }
 311 
 312 static int
 313 iwscnpoll(dev_t dev, short events, int anyyet, short *reventsp,
 314     struct pollhead **phpp)
 315 {
 316         iwscn_list_t    *lp;
 317         int             error;
 318 
 319         ASSERT(getminor(dev) == 0);
 320 
 321         lp = srhold();
 322         error = VOP_POLL(lp->wl_vp, events, anyyet, reventsp, phpp, NULL);
 323         srrele(lp);
 324 
 325         return (error);
 326 }
 327 
 328 static int
 329 iwscnioctl(dev_t dev, int cmd, intptr_t arg, int flag,
 330     cred_t *cred, int *rvalp)
 331 {
 332         iwscn_list_t    *lp;
 333         file_t          *f;
 334         char            modname[FMNAMESZ + 1] = " ";
 335         int             error = 0;
 336 
 337         ASSERT(getminor(dev) == 0);
 338 
 339         switch (cmd) {
 340         case SRIOCSREDIR:
 341                 /* Serialize all pushes of the redirection module */
 342                 mutex_enter(&iwscn_redirect_lock);
 343 
 344                 /*
 345                  * Find the vnode corresponding to the file descriptor
 346                  * argument and verify that it names a stream.
 347                  */
 348                 if ((f = getf((int)arg)) == NULL) {
 349                         mutex_exit(&iwscn_redirect_lock);
 350                         return (EBADF);
 351                 }
 352                 if (f->f_vnode->v_stream == NULL) {
 353                         releasef((int)arg);
 354                         mutex_exit(&iwscn_redirect_lock);
 355                         return (ENOSTR);
 356                 }
 357 
 358                 /*
 359                  * If the user is trying to redirect console output
 360                  * back to the underlying console via SRIOCSREDIR
 361                  * then they are evil and we'll stop them here.
 362                  */
 363                 if (str_vp(f->f_vnode) == str_vp(rwsconsvp)) {
 364                         releasef((int)arg);
 365                         mutex_exit(&iwscn_redirect_lock);
 366                         return (EINVAL);
 367                 }
 368 
 369                 /*
 370                  * Check if this stream already has the redirection
 371                  * module pushed onto it.  I_LOOK returns an error
 372                  * if there are no modules pushed onto the stream.
 373                  */
 374                 (void) strioctl(f->f_vnode, I_LOOK, (intptr_t)modname,
 375                     FKIOCTL, K_TO_K, cred, rvalp);
 376                 if (strcmp(modname, STRREDIR_MOD) != 0) {
 377 
 378                         /*
 379                          * Push a new instance of the redirecting module onto
 380                          * the stream, so that its close routine can notify
 381                          * us when the overall stream is closed.  (In turn,
 382                          * we'll then remove it from the redirection list.)
 383                          */
 384                         error = strioctl(f->f_vnode, I_PUSH,
 385                             (intptr_t)STRREDIR_MOD, FKIOCTL, K_TO_K,
 386                             cred, rvalp);
 387 
 388                         if (error != 0) {
 389                                 releasef((int)arg);
 390                                 mutex_exit(&iwscn_redirect_lock);
 391                                 return (error);
 392                         }
 393                 }
 394 
 395                 /* Push it onto the redirection stack */
 396                 mutex_enter(&iwscn_list_lock);
 397                 srpush(f->f_vnode, B_FALSE);
 398                 mutex_exit(&iwscn_list_lock);
 399 
 400                 releasef((int)arg);
 401                 mutex_exit(&iwscn_redirect_lock);
 402                 return (0);
 403 
 404         case SRIOCISREDIR:
 405                 /*
 406                  * Find the vnode corresponding to the file descriptor
 407                  * argument and verify that it names a stream.
 408                  */
 409                 if ((f = getf((int)arg)) == NULL) {
 410                         return (EBADF);
 411                 }
 412                 if (f->f_vnode->v_stream == NULL) {
 413                         releasef((int)arg);
 414                         return (ENOSTR);
 415                 }
 416 
 417                 lp = srhold();
 418                 *rvalp = (str_vp(f->f_vnode) == lp->wl_vp);
 419                 srrele(lp);
 420                 releasef((int)arg);
 421                 return (0);
 422 
 423         case I_POP:
 424                 /*
 425                  * We need to serialize I_POP operations with
 426                  * SRIOCSREDIR operations so we don't accidently
 427                  * remove the redirection module from a stream.
 428                  */
 429                 mutex_enter(&iwscn_redirect_lock);
 430                 lp = srhold();
 431 
 432                 /*
 433                  * Here we need to protect against process that might
 434                  * try to pop off the redirection module from the
 435                  * redirected stream.  Popping other modules is allowed.
 436                  *
 437                  * It's ok to hold iwscn_list_lock while doing the
 438                  * I_LOOK since it's such a simple operation.
 439                  */
 440                 (void) strioctl(lp->wl_vp, I_LOOK, (intptr_t)modname,
 441                     FKIOCTL, K_TO_K, cred, rvalp);
 442 
 443                 if (strcmp(STRREDIR_MOD, modname) == 0) {
 444                         srrele(lp);
 445                         mutex_exit(&iwscn_redirect_lock);
 446                         return (EINVAL);
 447                 }
 448 
 449                 /* Process the ioctl normally */
 450                 error = VOP_IOCTL(lp->wl_vp, cmd, arg, flag, cred, rvalp, NULL);
 451 
 452                 srrele(lp);
 453                 mutex_exit(&iwscn_redirect_lock);
 454                 return (error);
 455         }
 456 
 457         /* Process the ioctl normally */
 458         lp = srhold();
 459         error = VOP_IOCTL(lp->wl_vp, cmd, arg, flag, cred, rvalp, NULL);
 460         srrele(lp);
 461         return (error);
 462 }
 463 
 464 /* ARGSUSED */
 465 static int
 466 iwscnopen(dev_t *devp, int flag, int state, cred_t *cred)
 467 {
 468         iwscn_list_t    *lp;
 469         vnode_t         *vp = rwsconsvp;
 470 
 471         if (state != OTYP_CHR)
 472                 return (ENXIO);
 473 
 474         if (getminor(*devp) != 0)
 475                 return (ENXIO);
 476 
 477         /*
 478          * You can't really open us until the console subsystem
 479          * has been configured.
 480          */
 481         if (rwsconsvp == NULL)
 482                 return (ENXIO);
 483 
 484         /*
 485          * Check if this is the first open of this device or if
 486          * there is currently no redirection going on.  (Ie, we're
 487          * sending output to underlying console device.)
 488          */
 489         mutex_enter(&iwscn_list_lock);
 490         if ((iwscn_list == NULL) || (iwscn_list->wl_vp == str_vp(vp))) {
 491                 int             error = 0;
 492 
 493                 /* Don't hold the list lock across an VOP_OPEN */
 494                 mutex_exit(&iwscn_list_lock);
 495 
 496                 /*
 497                  * There is currently no redirection going on.
 498                  * pass this open request onto the console driver
 499                  */
 500                 error = VOP_OPEN(&vp, flag, cred, NULL);
 501                 if (error != 0)
 502                         return (error);
 503 
 504                 /* Re-acquire the list lock */
 505                 mutex_enter(&iwscn_list_lock);
 506 
 507                 if (iwscn_list == NULL) {
 508                         /* Save this vnode on the redirection list */
 509                         srpush(vp, B_TRUE);
 510                 } else {
 511                         /*
 512                          * In this case there must already be a copy of
 513                          * this vnode on the list, so we can free up this one.
 514                          */
 515                         (void) VOP_CLOSE(vp, flag, 1, (offset_t)0, cred, NULL);
 516                 }
 517         }
 518 
 519         /*
 520          * XXX This is an ugly legacy hack that has been around
 521          * forever.  This code is here because this driver (the
 522          * iwscn driver) is a character driver layered over a
 523          * streams driver.
 524          *
 525          * Normally streams recieve notification whenever a process
 526          * closes its last reference to that stream so that it can
 527          * clean up any signal handling related configuration.  (Ie,
 528          * when a stream is configured to deliver a signal to a
 529          * process upon certain events.)  This is a feature supported
 530          * by the streams framework.
 531          *
 532          * But character/block drivers don't recieve this type
 533          * of notification.  A character/block driver's close routine
 534          * is only invoked upon the last close of the device.  This
 535          * is an artifact of the multiple open/single close driver
 536          * model currently supported by solaris.
 537          *
 538          * So a problem occurs when a character driver layers itself
 539          * on top of a streams driver.  Since this driver doesn't always
 540          * receive a close notification when a process closes its
 541          * last reference to it, this driver can't tell the stream
 542          * it's layered upon to clean up any signal handling
 543          * configuration for that process.
 544          *
 545          * So here we hack around that by manually cleaning up the
 546          * signal handling list upon each open.  It doesn't guarantee
 547          * that the signaling handling data stored in the stream will
 548          * always be up to date, but it'll be more up to date than
 549          * it would be if we didn't do this.
 550          *
 551          * The real way to solve this problem would be to change
 552          * the device framework from an multiple open/single close
 553          * model to a multiple open/multiple close model.  Then
 554          * character/block drivers could pass on close requests
 555          * to streams layered underneath.
 556          */
 557         str_cn_clean(VTOS(rwsconsvp)->s_commonvp);
 558         for (lp = iwscn_list; lp != NULL; lp = lp->wl_next) {
 559                 ASSERT(lp->wl_vp->v_stream != NULL);
 560                 str_cn_clean(lp->wl_vp);
 561         }
 562 
 563         mutex_exit(&iwscn_list_lock);
 564         return (0);
 565 }
 566 
 567 /* ARGSUSED */
 568 static int
 569 iwscnclose(dev_t dev, int flag, int state, cred_t *cred)
 570 {
 571         iwscn_list_t    *lp;
 572 
 573         ASSERT(getminor(dev) == 0);
 574 
 575         if (state != OTYP_CHR)
 576                 return (ENXIO);
 577 
 578         mutex_enter(&iwscn_list_lock);
 579         /*
 580          * Remove each entry from the redirection list, terminate any
 581          * current operations, wait for them to finish, then free the entry.
 582          */
 583         while (iwscn_list != NULL) {
 584                 lp = srrm(iwscn_list->wl_vp);
 585                 ASSERT(lp != NULL);
 586                 srinterrupt(lp, B_TRUE);
 587 
 588                 if (lp->wl_is_console == B_TRUE)
 589                         /* Close the underlying console device. */
 590                         (void) VOP_CLOSE(lp->wl_vp, 0, 1, (offset_t)0, kcred,
 591                             NULL);
 592 
 593                 kmem_free(lp, sizeof (*lp));
 594         }
 595         mutex_exit(&iwscn_list_lock);
 596         return (0);
 597 }
 598 
 599 /*ARGSUSED*/
 600 static int
 601 iwscnattach(dev_info_t *devi, ddi_attach_cmd_t cmd)
 602 {
 603         /*
 604          * This is a pseudo device so there will never be more than
 605          * one instance attached at a time
 606          */
 607         ASSERT(iwscn_dip == NULL);
 608 
 609         if (ddi_create_minor_node(devi, "iwscn", S_IFCHR,
 610             0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
 611                 return (DDI_FAILURE);
 612         }
 613 
 614         iwscn_dip = devi;
 615         mutex_init(&iwscn_list_lock, NULL, MUTEX_DRIVER, NULL);
 616         mutex_init(&iwscn_redirect_lock, NULL, MUTEX_DRIVER, NULL);
 617         cv_init(&iwscn_list_cv, NULL, CV_DRIVER, NULL);
 618 
 619         return (DDI_SUCCESS);
 620 }
 621 
 622 /* ARGSUSED */
 623 static int
 624 iwscninfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
 625 {
 626         int error;
 627 
 628         switch (infocmd) {
 629         case DDI_INFO_DEVT2DEVINFO:
 630                 if (iwscn_dip == NULL) {
 631                         error = DDI_FAILURE;
 632                 } else {
 633                         *result = (void *)iwscn_dip;
 634                         error = DDI_SUCCESS;
 635                 }
 636                 break;
 637         case DDI_INFO_DEVT2INSTANCE:
 638                 *result = (void *)0;
 639                 error = DDI_SUCCESS;
 640                 break;
 641         default:
 642                 error = DDI_FAILURE;
 643         }
 644         return (error);
 645 }
 646 
 647 struct cb_ops   iwscn_cb_ops = {
 648         iwscnopen,              /* open */
 649         iwscnclose,             /* close */
 650         nodev,                  /* strategy */
 651         nodev,                  /* print */
 652         nodev,                  /* dump */
 653         iwscnread,              /* read */
 654         iwscnwrite,             /* write */
 655         iwscnioctl,             /* ioctl */
 656         nodev,                  /* devmap */
 657         nodev,                  /* mmap */
 658         nodev,                  /* segmap */
 659         iwscnpoll,              /* poll */
 660         ddi_prop_op,            /* cb_prop_op */
 661         NULL,                   /* streamtab  */
 662         D_MP                    /* Driver compatibility flag */
 663 };
 664 
 665 struct dev_ops  iwscn_ops = {
 666         DEVO_REV,               /* devo_rev, */
 667         0,                      /* refcnt  */
 668         iwscninfo,              /* info */
 669         nulldev,                /* identify */
 670         nulldev,                /* probe */
 671         iwscnattach,            /* attach */
 672         nodev,                  /* detach */
 673         nodev,                  /* reset */
 674         &iwscn_cb_ops,              /* driver operations */
 675         NULL,                   /* bus operations */
 676         NULL,                   /* power */
 677         ddi_quiesce_not_needed,         /* quiesce */
 678 };
 679 
 680 /*
 681  * Module linkage information for the kernel.
 682  */
 683 static struct modldrv modldrv = {
 684         &mod_driverops, /* Type of module.  This one is a pseudo driver */
 685         "Workstation Redirection driver",
 686         &iwscn_ops, /* driver ops */
 687 };
 688 
 689 static struct modlinkage modlinkage = {
 690         MODREV_1,
 691         &modldrv,
 692         NULL
 693 };
 694 
 695 int
 696 _init(void)
 697 {
 698         return (mod_install(&modlinkage));
 699 }
 700 
 701 int
 702 _fini(void)
 703 {
 704         return (EBUSY);
 705 }
 706 
 707 int
 708 _info(struct modinfo *modinfop)
 709 {
 710         return (mod_info(&modlinkage, modinfop));
 711 }