root/crypto/crypto.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. crypto_newsession
  2. crypto_freesession
  3. crypto_get_driverid
  4. crypto_kregister
  5. crypto_register
  6. crypto_unregister
  7. crypto_dispatch
  8. crypto_kdispatch
  9. crypto_kinvoke
  10. crypto_invoke
  11. crypto_freereq
  12. crypto_getreq
  13. crypto_thread
  14. crypto_done
  15. crypto_kdone
  16. crypto_getfeat

    1 /*      $OpenBSD: crypto.c,v 1.48 2006/05/31 23:01:44 tedu Exp $        */
    2 /*
    3  * The author of this code is Angelos D. Keromytis (angelos@cis.upenn.edu)
    4  *
    5  * This code was written by Angelos D. Keromytis in Athens, Greece, in
    6  * February 2000. Network Security Technologies Inc. (NSTI) kindly
    7  * supported the development of this code.
    8  *
    9  * Copyright (c) 2000, 2001 Angelos D. Keromytis
   10  *
   11  * Permission to use, copy, and modify this software with or without fee
   12  * is hereby granted, provided that this entire notice is included in
   13  * all source code copies of any software which is or includes a copy or
   14  * modification of this software.
   15  *
   16  * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR
   17  * IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY
   18  * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE
   19  * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR
   20  * PURPOSE.
   21  */
   22 
   23 #include <sys/param.h>
   24 #include <sys/systm.h>
   25 #include <sys/malloc.h>
   26 #include <sys/proc.h>
   27 #include <sys/pool.h>
   28 #include <crypto/cryptodev.h>
   29 
   30 struct cryptocap *crypto_drivers = NULL;
   31 int crypto_drivers_num = 0;
   32 
   33 struct pool cryptop_pool;
   34 struct pool cryptodesc_pool;
   35 int crypto_pool_initialized = 0;
   36 
   37 struct cryptop *crp_req_queue = NULL;
   38 struct cryptop **crp_req_queue_tail = NULL;
   39 
   40 struct cryptkop *krp_req_queue = NULL;
   41 struct cryptkop **krp_req_queue_tail = NULL;
   42 
   43 /*
   44  * Create a new session.
   45  */
   46 int
   47 crypto_newsession(u_int64_t *sid, struct cryptoini *cri, int hard)
   48 {
   49         u_int32_t hid, lid, hid2 = -1;
   50         struct cryptocap *cpc;
   51         struct cryptoini *cr;
   52         int err, s, turn = 0;
   53 
   54         if (crypto_drivers == NULL)
   55                 return EINVAL;
   56 
   57         s = splvm();
   58 
   59         /*
   60          * The algorithm we use here is pretty stupid; just use the
   61          * first driver that supports all the algorithms we need. Do
   62          * a double-pass over all the drivers, ignoring software ones
   63          * at first, to deal with cases of drivers that register after
   64          * the software one(s) --- e.g., PCMCIA crypto cards.
   65          *
   66          * XXX We need more smarts here (in real life too, but that's
   67          * XXX another story altogether).
   68          */
   69         do {
   70                 for (hid = 0; hid < crypto_drivers_num; hid++) {
   71                         cpc = &crypto_drivers[hid];
   72 
   73                         /*
   74                          * If it's not initialized or has remaining sessions
   75                          * referencing it, skip.
   76                          */
   77                         if (cpc->cc_newsession == NULL ||
   78                             (cpc->cc_flags & CRYPTOCAP_F_CLEANUP))
   79                                 continue;
   80 
   81                         if (cpc->cc_flags & CRYPTOCAP_F_SOFTWARE) {
   82                                 /*
   83                                  * First round of search, ignore
   84                                  * software drivers.
   85                                  */
   86                                 if (turn == 0)
   87                                         continue;
   88                         } else { /* !CRYPTOCAP_F_SOFTWARE */
   89                                 /* Second round of search, only software. */
   90                                 if (turn == 1)
   91                                         continue;
   92                         }
   93 
   94                         /* See if all the algorithms are supported. */
   95                         for (cr = cri; cr; cr = cr->cri_next) {
   96                                 if (cpc->cc_alg[cr->cri_alg] == 0)
   97                                         break;
   98                         }
   99 
  100                         /*
  101                          * If even one algorithm is not supported,
  102                          * keep searching.
  103                          */
  104                         if (cr != NULL)
  105                                 continue;
  106 
  107                         /*
  108                          * If we had a previous match, see how it compares
  109                          * to this one. Keep "remembering" whichever is
  110                          * the best of the two.
  111                          */
  112                         if (hid2 != -1) {
  113                                 /*
  114                                  * Compare session numbers, pick the one
  115                                  * with the lowest.
  116                                  * XXX Need better metrics, this will
  117                                  * XXX just do un-weighted round-robin.
  118                                  */
  119                                 if (crypto_drivers[hid].cc_sessions <=
  120                                     crypto_drivers[hid2].cc_sessions)
  121                                         hid2 = hid;
  122                         } else {
  123                                 /*
  124                                  * Remember this one, for future
  125                                  * comparisons.
  126                                  */
  127                                 hid2 = hid;
  128                         }
  129                 }
  130 
  131                 /*
  132                  * If we found something worth remembering, leave. The
  133                  * side-effect is that we will always prefer a hardware
  134                  * driver over the software one.
  135                  */
  136                 if (hid2 != -1)
  137                         break;
  138 
  139                 turn++;
  140 
  141                 /* If we only want hardware drivers, don't do second pass. */
  142         } while (turn <= 2 && hard == 0);
  143 
  144         hid = hid2;
  145 
  146         /*
  147          * Can't do everything in one session.
  148          *
  149          * XXX Fix this. We need to inject a "virtual" session
  150          * XXX layer right about here.
  151          */
  152 
  153         if (hid == -1) {
  154                 splx(s);
  155                 return EINVAL;
  156         }
  157 
  158         /* Call the driver initialization routine. */
  159         lid = hid; /* Pass the driver ID. */
  160         err = crypto_drivers[hid].cc_newsession(&lid, cri);
  161         if (err == 0) {
  162                 (*sid) = hid;
  163                 (*sid) <<= 32;
  164                 (*sid) |= (lid & 0xffffffff);
  165                 crypto_drivers[hid].cc_sessions++;
  166         }
  167 
  168         splx(s);
  169         return err;
  170 }
  171 
  172 /*
  173  * Delete an existing session (or a reserved session on an unregistered
  174  * driver).
  175  */
  176 int
  177 crypto_freesession(u_int64_t sid)
  178 {
  179         int err = 0, s;
  180         u_int32_t hid;
  181 
  182         if (crypto_drivers == NULL)
  183                 return EINVAL;
  184 
  185         /* Determine two IDs. */
  186         hid = (sid >> 32) & 0xffffffff;
  187 
  188         if (hid >= crypto_drivers_num)
  189                 return ENOENT;
  190 
  191         s = splvm();
  192 
  193         if (crypto_drivers[hid].cc_sessions)
  194                 crypto_drivers[hid].cc_sessions--;
  195 
  196         /* Call the driver cleanup routine, if available. */
  197         if (crypto_drivers[hid].cc_freesession)
  198                 err = crypto_drivers[hid].cc_freesession(sid);
  199 
  200         /*
  201          * If this was the last session of a driver marked as invalid,
  202          * make the entry available for reuse.
  203          */
  204         if ((crypto_drivers[hid].cc_flags & CRYPTOCAP_F_CLEANUP) &&
  205             crypto_drivers[hid].cc_sessions == 0)
  206                 bzero(&crypto_drivers[hid], sizeof(struct cryptocap));
  207 
  208         splx(s);
  209         return err;
  210 }
  211 
  212 /*
  213  * Find an empty slot.
  214  */
  215 int32_t
  216 crypto_get_driverid(u_int8_t flags)
  217 {
  218         struct cryptocap *newdrv;
  219         int i, s;
  220         
  221         s = splvm();
  222 
  223         if (crypto_drivers_num == 0) {
  224                 crypto_drivers_num = CRYPTO_DRIVERS_INITIAL;
  225                 crypto_drivers = malloc(crypto_drivers_num *
  226                     sizeof(struct cryptocap), M_CRYPTO_DATA, M_NOWAIT);
  227                 if (crypto_drivers == NULL) {
  228                         crypto_drivers_num = 0;
  229                         splx(s);
  230                         return -1;
  231                 }
  232 
  233                 bzero(crypto_drivers, crypto_drivers_num *
  234                     sizeof(struct cryptocap));
  235         }
  236 
  237         for (i = 0; i < crypto_drivers_num; i++) {
  238                 if (crypto_drivers[i].cc_process == NULL &&
  239                     !(crypto_drivers[i].cc_flags & CRYPTOCAP_F_CLEANUP) &&
  240                     crypto_drivers[i].cc_sessions == 0) {
  241                         crypto_drivers[i].cc_sessions = 1; /* Mark */
  242                         crypto_drivers[i].cc_flags = flags;
  243                         splx(s);
  244                         return i;
  245                 }
  246         }
  247 
  248         /* Out of entries, allocate some more. */
  249         if (i == crypto_drivers_num) {
  250                 /* Be careful about wrap-around. */
  251                 if (2 * crypto_drivers_num <= crypto_drivers_num) {
  252                         splx(s);
  253                         return -1;
  254                 }
  255 
  256                 newdrv = malloc(2 * crypto_drivers_num *
  257                     sizeof(struct cryptocap), M_CRYPTO_DATA, M_NOWAIT);
  258                 if (newdrv == NULL) {
  259                         splx(s);
  260                         return -1;
  261                 }
  262 
  263                 bcopy(crypto_drivers, newdrv,
  264                     crypto_drivers_num * sizeof(struct cryptocap));
  265                 bzero(&newdrv[crypto_drivers_num],
  266                     crypto_drivers_num * sizeof(struct cryptocap));
  267 
  268                 newdrv[i].cc_sessions = 1; /* Mark */
  269                 newdrv[i].cc_flags = flags;
  270                 crypto_drivers_num *= 2;
  271 
  272                 free(crypto_drivers, M_CRYPTO_DATA);
  273                 crypto_drivers = newdrv;
  274                 splx(s);
  275                 return i;
  276         }
  277 
  278         /* Shouldn't really get here... */
  279         splx(s);
  280         return -1;
  281 }
  282 
  283 /*
  284  * Register a crypto driver. It should be called once for each algorithm
  285  * supported by the driver.
  286  */
  287 int
  288 crypto_kregister(u_int32_t driverid, int *kalg,
  289     int (*kprocess)(struct cryptkop *))
  290 {
  291         int s, i;
  292 
  293         if (driverid >= crypto_drivers_num || kalg  == NULL ||
  294             crypto_drivers == NULL)
  295                 return EINVAL;
  296 
  297         s = splvm();
  298 
  299         for (i = 0; i < CRK_ALGORITHM_MAX; i++) {
  300                 /*
  301                  * XXX Do some performance testing to determine
  302                  * placing.  We probably need an auxiliary data
  303                  * structure that describes relative performances.
  304                  */
  305 
  306                 crypto_drivers[driverid].cc_kalg[i] = kalg[i];
  307         }
  308 
  309         crypto_drivers[driverid].cc_kprocess = kprocess;
  310 
  311         splx(s);
  312         return 0;
  313 }
  314 
  315 /* Register a crypto driver. */
  316 int
  317 crypto_register(u_int32_t driverid, int *alg,
  318     int (*newses)(u_int32_t *, struct cryptoini *),
  319     int (*freeses)(u_int64_t), int (*process)(struct cryptop *))
  320 {
  321         int s, i;
  322 
  323 
  324         if (driverid >= crypto_drivers_num || alg == NULL ||
  325             crypto_drivers == NULL)
  326                 return EINVAL;
  327         
  328         s = splvm();
  329 
  330         for (i = 0; i < CRYPTO_ALGORITHM_ALL; i++) {
  331                 /*
  332                  * XXX Do some performance testing to determine
  333                  * placing.  We probably need an auxiliary data
  334                  * structure that describes relative performances.
  335                  */
  336 
  337                 crypto_drivers[driverid].cc_alg[i] = alg[i];
  338         }
  339 
  340 
  341         crypto_drivers[driverid].cc_newsession = newses;
  342         crypto_drivers[driverid].cc_process = process;
  343         crypto_drivers[driverid].cc_freesession = freeses;
  344         crypto_drivers[driverid].cc_sessions = 0; /* Unmark */
  345 
  346         splx(s);
  347 
  348         return 0;
  349 }
  350 
  351 /*
  352  * Unregister a crypto driver. If there are pending sessions using it,
  353  * leave enough information around so that subsequent calls using those
  354  * sessions will correctly detect the driver being unregistered and reroute
  355  * the request.
  356  */
  357 int
  358 crypto_unregister(u_int32_t driverid, int alg)
  359 {
  360         int i = CRYPTO_ALGORITHM_MAX + 1, s;
  361         u_int32_t ses;
  362 
  363         s = splvm();
  364 
  365         /* Sanity checks. */
  366         if (driverid >= crypto_drivers_num || crypto_drivers == NULL ||
  367             ((alg <= 0 || alg > CRYPTO_ALGORITHM_MAX) &&
  368                 alg != CRYPTO_ALGORITHM_ALL) ||
  369             crypto_drivers[driverid].cc_alg[alg] == 0) {
  370                 splx(s);
  371                 return EINVAL;
  372         }
  373 
  374         if (alg != CRYPTO_ALGORITHM_ALL) {
  375                 crypto_drivers[driverid].cc_alg[alg] = 0;
  376 
  377                 /* Was this the last algorithm ? */
  378                 for (i = 1; i <= CRYPTO_ALGORITHM_MAX; i++)
  379                         if (crypto_drivers[driverid].cc_alg[i] != 0)
  380                                 break;
  381         }
  382 
  383         /*
  384          * If a driver unregistered its last algorithm or all of them
  385          * (alg == CRYPTO_ALGORITHM_ALL), cleanup its entry.
  386          */
  387         if (i == CRYPTO_ALGORITHM_MAX + 1 || alg == CRYPTO_ALGORITHM_ALL) {
  388                 ses = crypto_drivers[driverid].cc_sessions;
  389                 bzero(&crypto_drivers[driverid], sizeof(struct cryptocap));
  390                 if (ses != 0) {
  391                         /*
  392                          * If there are pending sessions, just mark as invalid.
  393                          */
  394                         crypto_drivers[driverid].cc_flags |= CRYPTOCAP_F_CLEANUP;
  395                         crypto_drivers[driverid].cc_sessions = ses;
  396                 }
  397         }
  398         splx(s);
  399         return 0;
  400 }
  401 
  402 /*
  403  * Add crypto request to a queue, to be processed by a kernel thread.
  404  */
  405 int
  406 crypto_dispatch(struct cryptop *crp)
  407 {
  408         int s;
  409         u_int32_t hid;
  410 
  411         s = splvm();
  412         /*
  413          * Keep track of ops per driver, for coallescing purposes. If
  414          * we have been given an invalid hid, we'll deal with in the
  415          * crypto_invoke(), through session migration.
  416          */
  417         hid = (crp->crp_sid >> 32) & 0xffffffff;
  418         if (hid < crypto_drivers_num)
  419                 crypto_drivers[hid].cc_queued++;
  420 
  421         crp->crp_next = NULL;
  422         if (crp_req_queue == NULL) {
  423                 crp_req_queue = crp;
  424                 crp_req_queue_tail = &(crp->crp_next);
  425                 splx(s);
  426                 wakeup(&crp_req_queue); /* Shared wait channel. */
  427         } else {
  428                 *crp_req_queue_tail = crp;
  429                 crp_req_queue_tail = &(crp->crp_next);
  430                 splx(s);
  431         }
  432         return 0;
  433 }
  434 
  435 int
  436 crypto_kdispatch(struct cryptkop *krp)
  437 {
  438         int s;
  439         
  440         s = splvm();
  441 
  442         krp->krp_next = NULL;
  443         if (krp_req_queue == NULL) {
  444                 krp_req_queue = krp;
  445                 krp_req_queue_tail = &(krp->krp_next);
  446                 splx(s);
  447                 wakeup(&crp_req_queue); /* Shared wait channel. */
  448         } else {
  449                 *krp_req_queue_tail = krp;
  450                 krp_req_queue_tail = &(krp->krp_next);
  451                 splx(s);
  452         }
  453         return 0;
  454 }
  455 
  456 /*
  457  * Dispatch an asymmetric crypto request to the appropriate crypto devices.
  458  */
  459 int
  460 crypto_kinvoke(struct cryptkop *krp)
  461 {
  462         extern int cryptodevallowsoft;
  463         u_int32_t hid;
  464         int error;
  465 
  466         /* Sanity checks. */
  467         if (krp == NULL || krp->krp_callback == NULL)
  468                 return (EINVAL);
  469 
  470         for (hid = 0; hid < crypto_drivers_num; hid++) {
  471                 if ((crypto_drivers[hid].cc_flags & CRYPTOCAP_F_SOFTWARE) &&
  472                     cryptodevallowsoft == 0)
  473                         continue;
  474                 if (crypto_drivers[hid].cc_kprocess == NULL)
  475                         continue;
  476                 if ((crypto_drivers[hid].cc_kalg[krp->krp_op] &
  477                     CRYPTO_ALG_FLAG_SUPPORTED) == 0)
  478                         continue;
  479                 break;
  480         }
  481 
  482         if (hid == crypto_drivers_num) {
  483                 krp->krp_status = ENODEV;
  484                 crypto_kdone(krp);
  485                 return (0);
  486         }
  487 
  488         krp->krp_hid = hid;
  489 
  490         crypto_drivers[hid].cc_koperations++;
  491 
  492         error = crypto_drivers[hid].cc_kprocess(krp);
  493         if (error) {
  494                 krp->krp_status = error;
  495                 crypto_kdone(krp);
  496         }
  497         return (0);
  498 }
  499 
  500 /*
  501  * Dispatch a crypto request to the appropriate crypto devices.
  502  */
  503 int
  504 crypto_invoke(struct cryptop *crp)
  505 {
  506         struct cryptodesc *crd;
  507         u_int64_t nid;
  508         u_int32_t hid;
  509         int error;
  510 
  511         /* Sanity checks. */
  512         if (crp == NULL || crp->crp_callback == NULL)
  513                 return EINVAL;
  514 
  515         if (crp->crp_desc == NULL || crypto_drivers == NULL) {
  516                 crp->crp_etype = EINVAL;
  517                 crypto_done(crp);
  518                 return 0;
  519         }
  520 
  521         hid = (crp->crp_sid >> 32) & 0xffffffff;
  522         if (hid >= crypto_drivers_num)
  523                 goto migrate;
  524 
  525         crypto_drivers[hid].cc_queued--;
  526 
  527         if (crypto_drivers[hid].cc_flags & CRYPTOCAP_F_CLEANUP) {
  528                 crypto_freesession(crp->crp_sid);
  529                 goto migrate;
  530         }
  531 
  532         if (crypto_drivers[hid].cc_process == NULL)
  533                 goto migrate;
  534 
  535         crypto_drivers[hid].cc_operations++;
  536         crypto_drivers[hid].cc_bytes += crp->crp_ilen;
  537 
  538         error = crypto_drivers[hid].cc_process(crp);
  539         if (error) {
  540                 if (error == ERESTART) {
  541                         /* Unregister driver and migrate session. */
  542                         crypto_unregister(hid, CRYPTO_ALGORITHM_ALL);
  543                         goto migrate;
  544                 } else {
  545                         crp->crp_etype = error;
  546                         crypto_done(crp);
  547                 }
  548         }
  549 
  550         return 0;
  551 
  552  migrate:
  553         /* Migrate session. */
  554         for (crd = crp->crp_desc; crd->crd_next; crd = crd->crd_next)
  555                 crd->CRD_INI.cri_next = &(crd->crd_next->CRD_INI);
  556 
  557         if (crypto_newsession(&nid, &(crp->crp_desc->CRD_INI), 0) == 0)
  558                 crp->crp_sid = nid;
  559 
  560         crp->crp_etype = EAGAIN;
  561         crypto_done(crp);
  562         return 0;
  563 }
  564 
  565 /*
  566  * Release a set of crypto descriptors.
  567  */
  568 void
  569 crypto_freereq(struct cryptop *crp)
  570 {
  571         struct cryptodesc *crd;
  572         int s;
  573 
  574         if (crp == NULL)
  575                 return;
  576 
  577         s = splvm();
  578 
  579         while ((crd = crp->crp_desc) != NULL) {
  580                 crp->crp_desc = crd->crd_next;
  581                 pool_put(&cryptodesc_pool, crd);
  582         }
  583 
  584         pool_put(&cryptop_pool, crp);
  585         splx(s);
  586 }
  587 
  588 /*
  589  * Acquire a set of crypto descriptors.
  590  */
  591 struct cryptop *
  592 crypto_getreq(int num)
  593 {
  594         struct cryptodesc *crd;
  595         struct cryptop *crp;
  596         int s;
  597         
  598         s = splvm();
  599 
  600         if (crypto_pool_initialized == 0) {
  601                 pool_init(&cryptop_pool, sizeof(struct cryptop), 0, 0,
  602                     0, "cryptop", NULL);
  603                 pool_init(&cryptodesc_pool, sizeof(struct cryptodesc), 0, 0,
  604                     0, "cryptodesc", NULL);
  605                 crypto_pool_initialized = 1;
  606         }
  607 
  608         crp = pool_get(&cryptop_pool, PR_NOWAIT);
  609         if (crp == NULL) {
  610                 splx(s);
  611                 return NULL;
  612         }
  613         bzero(crp, sizeof(struct cryptop));
  614 
  615         while (num--) {
  616                 crd = pool_get(&cryptodesc_pool, PR_NOWAIT);
  617                 if (crd == NULL) {
  618                         splx(s);
  619                         crypto_freereq(crp);
  620                         return NULL;
  621                 }
  622 
  623                 bzero(crd, sizeof(struct cryptodesc));
  624                 crd->crd_next = crp->crp_desc;
  625                 crp->crp_desc = crd;
  626         }
  627 
  628         splx(s);
  629         return crp;
  630 }
  631 
  632 /*
  633  * Crypto thread, runs as a kernel thread to process crypto requests.
  634  */
  635 void
  636 crypto_thread(void)
  637 {
  638         struct cryptop *crp;
  639         struct cryptkop *krp;
  640         int s;
  641 
  642         s = splvm();
  643 
  644         for (;;) {
  645                 crp = crp_req_queue;
  646                 krp = krp_req_queue;
  647                 if (crp == NULL && krp == NULL) {
  648                         (void)tsleep(&crp_req_queue, PLOCK, "crypto_wait", 0);
  649                         continue;
  650                 }
  651 
  652                 if (crp) {
  653                         /* Remove from the queue. */
  654                         crp_req_queue = crp->crp_next;
  655                         crypto_invoke(crp);
  656                 }
  657                 if (krp) {
  658                         /* Remove from the queue. */
  659                         krp_req_queue = krp->krp_next;
  660                         crypto_kinvoke(krp);
  661                 }
  662         }
  663 }
  664 
  665 /*
  666  * Invoke the callback on behalf of the driver.
  667  */
  668 void
  669 crypto_done(struct cryptop *crp)
  670 {
  671         crp->crp_flags |= CRYPTO_F_DONE;
  672         crp->crp_callback(crp);
  673 }
  674 
  675 /*
  676  * Invoke the callback on behalf of the driver.
  677  */
  678 void
  679 crypto_kdone(struct cryptkop *krp)
  680 {
  681         krp->krp_callback(krp);
  682 }
  683 
  684 int
  685 crypto_getfeat(int *featp)
  686 {
  687         extern int cryptodevallowsoft, userasymcrypto;
  688         int hid, kalg, feat = 0;
  689 
  690         if (userasymcrypto == 0)
  691                 goto out;         
  692         for (hid = 0; hid < crypto_drivers_num; hid++) {
  693                 if ((crypto_drivers[hid].cc_flags & CRYPTOCAP_F_SOFTWARE) &&
  694                     cryptodevallowsoft == 0) {
  695                         continue;
  696                 }
  697                 if (crypto_drivers[hid].cc_kprocess == NULL)
  698                         continue;
  699                 for (kalg = 0; kalg < CRK_ALGORITHM_MAX; kalg++)
  700                         if ((crypto_drivers[hid].cc_kalg[kalg] &
  701                             CRYPTO_ALG_FLAG_SUPPORTED) != 0)
  702                                 feat |=  1 << kalg;
  703         }
  704 out:
  705         *featp = feat;
  706         return (0);
  707 }

/* [<][>][^][v][top][bottom][index][help] */