Annotation of embedaddon/curl/lib/conncache.c, revision 1.1

1.1     ! misho       1: /***************************************************************************
        !             2:  *                                  _   _ ____  _
        !             3:  *  Project                     ___| | | |  _ \| |
        !             4:  *                             / __| | | | |_) | |
        !             5:  *                            | (__| |_| |  _ <| |___
        !             6:  *                             \___|\___/|_| \_\_____|
        !             7:  *
        !             8:  * Copyright (C) 2012 - 2016, Linus Nielsen Feltzing, <linus@haxx.se>
        !             9:  * Copyright (C) 2012 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
        !            10:  *
        !            11:  * This software is licensed as described in the file COPYING, which
        !            12:  * you should have received as part of this distribution. The terms
        !            13:  * are also available at https://curl.haxx.se/docs/copyright.html.
        !            14:  *
        !            15:  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
        !            16:  * copies of the Software, and permit persons to whom the Software is
        !            17:  * furnished to do so, under the terms of the COPYING file.
        !            18:  *
        !            19:  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
        !            20:  * KIND, either express or implied.
        !            21:  *
        !            22:  ***************************************************************************/
        !            23: 
        !            24: #include "curl_setup.h"
        !            25: 
        !            26: #include <curl/curl.h>
        !            27: 
        !            28: #include "urldata.h"
        !            29: #include "url.h"
        !            30: #include "progress.h"
        !            31: #include "multiif.h"
        !            32: #include "sendf.h"
        !            33: #include "conncache.h"
        !            34: #include "share.h"
        !            35: #include "sigpipe.h"
        !            36: #include "connect.h"
        !            37: 
        !            38: /* The last 3 #include files should be in this order */
        !            39: #include "curl_printf.h"
        !            40: #include "curl_memory.h"
        !            41: #include "memdebug.h"
        !            42: 
        !            43: #define HASHKEY_SIZE 128
        !            44: 
        !            45: static void conn_llist_dtor(void *user, void *element)
        !            46: {
        !            47:   struct connectdata *conn = element;
        !            48:   (void)user;
        !            49:   conn->bundle = NULL;
        !            50: }
        !            51: 
        !            52: static CURLcode bundle_create(struct Curl_easy *data,
        !            53:                               struct connectbundle **cb_ptr)
        !            54: {
        !            55:   (void)data;
        !            56:   DEBUGASSERT(*cb_ptr == NULL);
        !            57:   *cb_ptr = malloc(sizeof(struct connectbundle));
        !            58:   if(!*cb_ptr)
        !            59:     return CURLE_OUT_OF_MEMORY;
        !            60: 
        !            61:   (*cb_ptr)->num_connections = 0;
        !            62:   (*cb_ptr)->multiuse = BUNDLE_UNKNOWN;
        !            63: 
        !            64:   Curl_llist_init(&(*cb_ptr)->conn_list, (curl_llist_dtor) conn_llist_dtor);
        !            65:   return CURLE_OK;
        !            66: }
        !            67: 
        !            68: static void bundle_destroy(struct connectbundle *cb_ptr)
        !            69: {
        !            70:   if(!cb_ptr)
        !            71:     return;
        !            72: 
        !            73:   Curl_llist_destroy(&cb_ptr->conn_list, NULL);
        !            74: 
        !            75:   free(cb_ptr);
        !            76: }
        !            77: 
        !            78: /* Add a connection to a bundle */
        !            79: static void bundle_add_conn(struct connectbundle *cb_ptr,
        !            80:                             struct connectdata *conn)
        !            81: {
        !            82:   Curl_llist_insert_next(&cb_ptr->conn_list, cb_ptr->conn_list.tail, conn,
        !            83:                          &conn->bundle_node);
        !            84:   conn->bundle = cb_ptr;
        !            85:   cb_ptr->num_connections++;
        !            86: }
        !            87: 
        !            88: /* Remove a connection from a bundle */
        !            89: static int bundle_remove_conn(struct connectbundle *cb_ptr,
        !            90:                               struct connectdata *conn)
        !            91: {
        !            92:   struct curl_llist_element *curr;
        !            93: 
        !            94:   curr = cb_ptr->conn_list.head;
        !            95:   while(curr) {
        !            96:     if(curr->ptr == conn) {
        !            97:       Curl_llist_remove(&cb_ptr->conn_list, curr, NULL);
        !            98:       cb_ptr->num_connections--;
        !            99:       conn->bundle = NULL;
        !           100:       return 1; /* we removed a handle */
        !           101:     }
        !           102:     curr = curr->next;
        !           103:   }
        !           104:   DEBUGASSERT(0);
        !           105:   return 0;
        !           106: }
        !           107: 
        !           108: static void free_bundle_hash_entry(void *freethis)
        !           109: {
        !           110:   struct connectbundle *b = (struct connectbundle *) freethis;
        !           111: 
        !           112:   bundle_destroy(b);
        !           113: }
        !           114: 
        !           115: int Curl_conncache_init(struct conncache *connc, int size)
        !           116: {
        !           117:   int rc;
        !           118: 
        !           119:   /* allocate a new easy handle to use when closing cached connections */
        !           120:   connc->closure_handle = curl_easy_init();
        !           121:   if(!connc->closure_handle)
        !           122:     return 1; /* bad */
        !           123: 
        !           124:   rc = Curl_hash_init(&connc->hash, size, Curl_hash_str,
        !           125:                       Curl_str_key_compare, free_bundle_hash_entry);
        !           126:   if(rc)
        !           127:     Curl_close(&connc->closure_handle);
        !           128:   else
        !           129:     connc->closure_handle->state.conn_cache = connc;
        !           130: 
        !           131:   return rc;
        !           132: }
        !           133: 
        !           134: void Curl_conncache_destroy(struct conncache *connc)
        !           135: {
        !           136:   if(connc)
        !           137:     Curl_hash_destroy(&connc->hash);
        !           138: }
        !           139: 
        !           140: /* creates a key to find a bundle for this connection */
        !           141: static void hashkey(struct connectdata *conn, char *buf,
        !           142:                     size_t len,  /* something like 128 is fine */
        !           143:                     const char **hostp)
        !           144: {
        !           145:   const char *hostname;
        !           146:   long port = conn->remote_port;
        !           147: 
        !           148:   if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
        !           149:     hostname = conn->http_proxy.host.name;
        !           150:     port = conn->port;
        !           151:   }
        !           152:   else if(conn->bits.conn_to_host)
        !           153:     hostname = conn->conn_to_host.name;
        !           154:   else
        !           155:     hostname = conn->host.name;
        !           156: 
        !           157:   if(hostp)
        !           158:     /* report back which name we used */
        !           159:     *hostp = hostname;
        !           160: 
        !           161:   /* put the number first so that the hostname gets cut off if too long */
        !           162:   msnprintf(buf, len, "%ld%s", port, hostname);
        !           163: }
        !           164: 
        !           165: void Curl_conncache_unlock(struct Curl_easy *data)
        !           166: {
        !           167:   CONN_UNLOCK(data);
        !           168: }
        !           169: 
        !           170: /* Returns number of connections currently held in the connection cache.
        !           171:    Locks/unlocks the cache itself!
        !           172: */
        !           173: size_t Curl_conncache_size(struct Curl_easy *data)
        !           174: {
        !           175:   size_t num;
        !           176:   CONN_LOCK(data);
        !           177:   num = data->state.conn_cache->num_conn;
        !           178:   CONN_UNLOCK(data);
        !           179:   return num;
        !           180: }
        !           181: 
        !           182: /* Look up the bundle with all the connections to the same host this
        !           183:    connectdata struct is setup to use.
        !           184: 
        !           185:    **NOTE**: When it returns, it holds the connection cache lock! */
        !           186: struct connectbundle *Curl_conncache_find_bundle(struct connectdata *conn,
        !           187:                                                  struct conncache *connc,
        !           188:                                                  const char **hostp)
        !           189: {
        !           190:   struct connectbundle *bundle = NULL;
        !           191:   CONN_LOCK(conn->data);
        !           192:   if(connc) {
        !           193:     char key[HASHKEY_SIZE];
        !           194:     hashkey(conn, key, sizeof(key), hostp);
        !           195:     bundle = Curl_hash_pick(&connc->hash, key, strlen(key));
        !           196:   }
        !           197: 
        !           198:   return bundle;
        !           199: }
        !           200: 
        !           201: static bool conncache_add_bundle(struct conncache *connc,
        !           202:                                  char *key,
        !           203:                                  struct connectbundle *bundle)
        !           204: {
        !           205:   void *p = Curl_hash_add(&connc->hash, key, strlen(key), bundle);
        !           206: 
        !           207:   return p?TRUE:FALSE;
        !           208: }
        !           209: 
        !           210: static void conncache_remove_bundle(struct conncache *connc,
        !           211:                                     struct connectbundle *bundle)
        !           212: {
        !           213:   struct curl_hash_iterator iter;
        !           214:   struct curl_hash_element *he;
        !           215: 
        !           216:   if(!connc)
        !           217:     return;
        !           218: 
        !           219:   Curl_hash_start_iterate(&connc->hash, &iter);
        !           220: 
        !           221:   he = Curl_hash_next_element(&iter);
        !           222:   while(he) {
        !           223:     if(he->ptr == bundle) {
        !           224:       /* The bundle is destroyed by the hash destructor function,
        !           225:          free_bundle_hash_entry() */
        !           226:       Curl_hash_delete(&connc->hash, he->key, he->key_len);
        !           227:       return;
        !           228:     }
        !           229: 
        !           230:     he = Curl_hash_next_element(&iter);
        !           231:   }
        !           232: }
        !           233: 
        !           234: CURLcode Curl_conncache_add_conn(struct conncache *connc,
        !           235:                                  struct connectdata *conn)
        !           236: {
        !           237:   CURLcode result = CURLE_OK;
        !           238:   struct connectbundle *bundle;
        !           239:   struct connectbundle *new_bundle = NULL;
        !           240:   struct Curl_easy *data = conn->data;
        !           241: 
        !           242:   /* *find_bundle() locks the connection cache */
        !           243:   bundle = Curl_conncache_find_bundle(conn, data->state.conn_cache, NULL);
        !           244:   if(!bundle) {
        !           245:     int rc;
        !           246:     char key[HASHKEY_SIZE];
        !           247: 
        !           248:     result = bundle_create(data, &new_bundle);
        !           249:     if(result) {
        !           250:       goto unlock;
        !           251:     }
        !           252: 
        !           253:     hashkey(conn, key, sizeof(key), NULL);
        !           254:     rc = conncache_add_bundle(data->state.conn_cache, key, new_bundle);
        !           255: 
        !           256:     if(!rc) {
        !           257:       bundle_destroy(new_bundle);
        !           258:       result = CURLE_OUT_OF_MEMORY;
        !           259:       goto unlock;
        !           260:     }
        !           261:     bundle = new_bundle;
        !           262:   }
        !           263: 
        !           264:   bundle_add_conn(bundle, conn);
        !           265:   conn->connection_id = connc->next_connection_id++;
        !           266:   connc->num_conn++;
        !           267: 
        !           268:   DEBUGF(infof(conn->data, "Added connection %ld. "
        !           269:                "The cache now contains %zu members\n",
        !           270:                conn->connection_id, connc->num_conn));
        !           271: 
        !           272:   unlock:
        !           273:   CONN_UNLOCK(data);
        !           274: 
        !           275:   return result;
        !           276: }
        !           277: 
        !           278: /*
        !           279:  * Removes the connectdata object from the connection cache *and* clears the
        !           280:  * ->data pointer association. Pass TRUE/FALSE in the 'lock' argument
        !           281:  * depending on if the parent function already holds the lock or not.
        !           282:  */
        !           283: void Curl_conncache_remove_conn(struct Curl_easy *data,
        !           284:                                 struct connectdata *conn, bool lock)
        !           285: {
        !           286:   struct connectbundle *bundle = conn->bundle;
        !           287:   struct conncache *connc = data->state.conn_cache;
        !           288: 
        !           289:   /* The bundle pointer can be NULL, since this function can be called
        !           290:      due to a failed connection attempt, before being added to a bundle */
        !           291:   if(bundle) {
        !           292:     if(lock) {
        !           293:       CONN_LOCK(data);
        !           294:     }
        !           295:     bundle_remove_conn(bundle, conn);
        !           296:     if(bundle->num_connections == 0)
        !           297:       conncache_remove_bundle(connc, bundle);
        !           298:     conn->bundle = NULL; /* removed from it */
        !           299:     if(connc) {
        !           300:       connc->num_conn--;
        !           301:       DEBUGF(infof(data, "The cache now contains %zu members\n",
        !           302:                    connc->num_conn));
        !           303:     }
        !           304:     conn->data = NULL; /* clear the association */
        !           305:     if(lock) {
        !           306:       CONN_UNLOCK(data);
        !           307:     }
        !           308:   }
        !           309: }
        !           310: 
        !           311: /* This function iterates the entire connection cache and calls the function
        !           312:    func() with the connection pointer as the first argument and the supplied
        !           313:    'param' argument as the other.
        !           314: 
        !           315:    The conncache lock is still held when the callback is called. It needs it,
        !           316:    so that it can safely continue traversing the lists once the callback
        !           317:    returns.
        !           318: 
        !           319:    Returns 1 if the loop was aborted due to the callback's return code.
        !           320: 
        !           321:    Return 0 from func() to continue the loop, return 1 to abort it.
        !           322:  */
        !           323: bool Curl_conncache_foreach(struct Curl_easy *data,
        !           324:                             struct conncache *connc,
        !           325:                             void *param,
        !           326:                             int (*func)(struct connectdata *conn, void *param))
        !           327: {
        !           328:   struct curl_hash_iterator iter;
        !           329:   struct curl_llist_element *curr;
        !           330:   struct curl_hash_element *he;
        !           331: 
        !           332:   if(!connc)
        !           333:     return FALSE;
        !           334: 
        !           335:   CONN_LOCK(data);
        !           336:   Curl_hash_start_iterate(&connc->hash, &iter);
        !           337: 
        !           338:   he = Curl_hash_next_element(&iter);
        !           339:   while(he) {
        !           340:     struct connectbundle *bundle;
        !           341: 
        !           342:     bundle = he->ptr;
        !           343:     he = Curl_hash_next_element(&iter);
        !           344: 
        !           345:     curr = bundle->conn_list.head;
        !           346:     while(curr) {
        !           347:       /* Yes, we need to update curr before calling func(), because func()
        !           348:          might decide to remove the connection */
        !           349:       struct connectdata *conn = curr->ptr;
        !           350:       curr = curr->next;
        !           351: 
        !           352:       if(1 == func(conn, param)) {
        !           353:         CONN_UNLOCK(data);
        !           354:         return TRUE;
        !           355:       }
        !           356:     }
        !           357:   }
        !           358:   CONN_UNLOCK(data);
        !           359:   return FALSE;
        !           360: }
        !           361: 
        !           362: /* Return the first connection found in the cache. Used when closing all
        !           363:    connections.
        !           364: 
        !           365:    NOTE: no locking is done here as this is presumably only done when cleaning
        !           366:    up a cache!
        !           367: */
        !           368: static struct connectdata *
        !           369: conncache_find_first_connection(struct conncache *connc)
        !           370: {
        !           371:   struct curl_hash_iterator iter;
        !           372:   struct curl_hash_element *he;
        !           373:   struct connectbundle *bundle;
        !           374: 
        !           375:   Curl_hash_start_iterate(&connc->hash, &iter);
        !           376: 
        !           377:   he = Curl_hash_next_element(&iter);
        !           378:   while(he) {
        !           379:     struct curl_llist_element *curr;
        !           380:     bundle = he->ptr;
        !           381: 
        !           382:     curr = bundle->conn_list.head;
        !           383:     if(curr) {
        !           384:       return curr->ptr;
        !           385:     }
        !           386: 
        !           387:     he = Curl_hash_next_element(&iter);
        !           388:   }
        !           389: 
        !           390:   return NULL;
        !           391: }
        !           392: 
        !           393: /*
        !           394:  * Give ownership of a connection back to the connection cache. Might
        !           395:  * disconnect the oldest existing in there to make space.
        !           396:  *
        !           397:  * Return TRUE if stored, FALSE if closed.
        !           398:  */
        !           399: bool Curl_conncache_return_conn(struct Curl_easy *data,
        !           400:                                 struct connectdata *conn)
        !           401: {
        !           402:   /* data->multi->maxconnects can be negative, deal with it. */
        !           403:   size_t maxconnects =
        !           404:     (data->multi->maxconnects < 0) ? data->multi->num_easy * 4:
        !           405:     data->multi->maxconnects;
        !           406:   struct connectdata *conn_candidate = NULL;
        !           407: 
        !           408:   conn->lastused = Curl_now(); /* it was used up until now */
        !           409:   if(maxconnects > 0 &&
        !           410:      Curl_conncache_size(data) > maxconnects) {
        !           411:     infof(data, "Connection cache is full, closing the oldest one.\n");
        !           412: 
        !           413:     conn_candidate = Curl_conncache_extract_oldest(data);
        !           414:     if(conn_candidate) {
        !           415:       /* the winner gets the honour of being disconnected */
        !           416:       (void)Curl_disconnect(data, conn_candidate, /* dead_connection */ FALSE);
        !           417:     }
        !           418:   }
        !           419: 
        !           420:   return (conn_candidate == conn) ? FALSE : TRUE;
        !           421: 
        !           422: }
        !           423: 
        !           424: /*
        !           425:  * This function finds the connection in the connection bundle that has been
        !           426:  * unused for the longest time.
        !           427:  *
        !           428:  * Does not lock the connection cache!
        !           429:  *
        !           430:  * Returns the pointer to the oldest idle connection, or NULL if none was
        !           431:  * found.
        !           432:  */
        !           433: struct connectdata *
        !           434: Curl_conncache_extract_bundle(struct Curl_easy *data,
        !           435:                               struct connectbundle *bundle)
        !           436: {
        !           437:   struct curl_llist_element *curr;
        !           438:   timediff_t highscore = -1;
        !           439:   timediff_t score;
        !           440:   struct curltime now;
        !           441:   struct connectdata *conn_candidate = NULL;
        !           442:   struct connectdata *conn;
        !           443: 
        !           444:   (void)data;
        !           445: 
        !           446:   now = Curl_now();
        !           447: 
        !           448:   curr = bundle->conn_list.head;
        !           449:   while(curr) {
        !           450:     conn = curr->ptr;
        !           451: 
        !           452:     if(!CONN_INUSE(conn) && !conn->data) {
        !           453:       /* Set higher score for the age passed since the connection was used */
        !           454:       score = Curl_timediff(now, conn->lastused);
        !           455: 
        !           456:       if(score > highscore) {
        !           457:         highscore = score;
        !           458:         conn_candidate = conn;
        !           459:       }
        !           460:     }
        !           461:     curr = curr->next;
        !           462:   }
        !           463:   if(conn_candidate) {
        !           464:     /* remove it to prevent another thread from nicking it */
        !           465:     bundle_remove_conn(bundle, conn_candidate);
        !           466:     data->state.conn_cache->num_conn--;
        !           467:     DEBUGF(infof(data, "The cache now contains %zu members\n",
        !           468:                  data->state.conn_cache->num_conn));
        !           469:     conn_candidate->data = data; /* associate! */
        !           470:   }
        !           471: 
        !           472:   return conn_candidate;
        !           473: }
        !           474: 
        !           475: /*
        !           476:  * This function finds the connection in the connection cache that has been
        !           477:  * unused for the longest time and extracts that from the bundle.
        !           478:  *
        !           479:  * Returns the pointer to the connection, or NULL if none was found.
        !           480:  */
        !           481: struct connectdata *
        !           482: Curl_conncache_extract_oldest(struct Curl_easy *data)
        !           483: {
        !           484:   struct conncache *connc = data->state.conn_cache;
        !           485:   struct curl_hash_iterator iter;
        !           486:   struct curl_llist_element *curr;
        !           487:   struct curl_hash_element *he;
        !           488:   timediff_t highscore =- 1;
        !           489:   timediff_t score;
        !           490:   struct curltime now;
        !           491:   struct connectdata *conn_candidate = NULL;
        !           492:   struct connectbundle *bundle;
        !           493:   struct connectbundle *bundle_candidate = NULL;
        !           494: 
        !           495:   now = Curl_now();
        !           496: 
        !           497:   CONN_LOCK(data);
        !           498:   Curl_hash_start_iterate(&connc->hash, &iter);
        !           499: 
        !           500:   he = Curl_hash_next_element(&iter);
        !           501:   while(he) {
        !           502:     struct connectdata *conn;
        !           503: 
        !           504:     bundle = he->ptr;
        !           505: 
        !           506:     curr = bundle->conn_list.head;
        !           507:     while(curr) {
        !           508:       conn = curr->ptr;
        !           509: 
        !           510:       if(!CONN_INUSE(conn) && !conn->data && !conn->bits.close &&
        !           511:          !conn->bits.connect_only) {
        !           512:         /* Set higher score for the age passed since the connection was used */
        !           513:         score = Curl_timediff(now, conn->lastused);
        !           514: 
        !           515:         if(score > highscore) {
        !           516:           highscore = score;
        !           517:           conn_candidate = conn;
        !           518:           bundle_candidate = bundle;
        !           519:         }
        !           520:       }
        !           521:       curr = curr->next;
        !           522:     }
        !           523: 
        !           524:     he = Curl_hash_next_element(&iter);
        !           525:   }
        !           526:   if(conn_candidate) {
        !           527:     /* remove it to prevent another thread from nicking it */
        !           528:     bundle_remove_conn(bundle_candidate, conn_candidate);
        !           529:     connc->num_conn--;
        !           530:     DEBUGF(infof(data, "The cache now contains %zu members\n",
        !           531:                  connc->num_conn));
        !           532:     conn_candidate->data = data; /* associate! */
        !           533:   }
        !           534:   CONN_UNLOCK(data);
        !           535: 
        !           536:   return conn_candidate;
        !           537: }
        !           538: 
        !           539: void Curl_conncache_close_all_connections(struct conncache *connc)
        !           540: {
        !           541:   struct connectdata *conn;
        !           542: 
        !           543:   conn = conncache_find_first_connection(connc);
        !           544:   while(conn) {
        !           545:     SIGPIPE_VARIABLE(pipe_st);
        !           546:     conn->data = connc->closure_handle;
        !           547: 
        !           548:     sigpipe_ignore(conn->data, &pipe_st);
        !           549:     /* This will remove the connection from the cache */
        !           550:     connclose(conn, "kill all");
        !           551:     (void)Curl_disconnect(connc->closure_handle, conn, FALSE);
        !           552:     sigpipe_restore(&pipe_st);
        !           553: 
        !           554:     conn = conncache_find_first_connection(connc);
        !           555:   }
        !           556: 
        !           557:   if(connc->closure_handle) {
        !           558:     SIGPIPE_VARIABLE(pipe_st);
        !           559:     sigpipe_ignore(connc->closure_handle, &pipe_st);
        !           560: 
        !           561:     Curl_hostcache_clean(connc->closure_handle,
        !           562:                          connc->closure_handle->dns.hostcache);
        !           563:     Curl_close(&connc->closure_handle);
        !           564:     sigpipe_restore(&pipe_st);
        !           565:   }
        !           566: }
        !           567: 
        !           568: #if 0
        !           569: /* Useful for debugging the connection cache */
        !           570: void Curl_conncache_print(struct conncache *connc)
        !           571: {
        !           572:   struct curl_hash_iterator iter;
        !           573:   struct curl_llist_element *curr;
        !           574:   struct curl_hash_element *he;
        !           575: 
        !           576:   if(!connc)
        !           577:     return;
        !           578: 
        !           579:   fprintf(stderr, "=Bundle cache=\n");
        !           580: 
        !           581:   Curl_hash_start_iterate(connc->hash, &iter);
        !           582: 
        !           583:   he = Curl_hash_next_element(&iter);
        !           584:   while(he) {
        !           585:     struct connectbundle *bundle;
        !           586:     struct connectdata *conn;
        !           587: 
        !           588:     bundle = he->ptr;
        !           589: 
        !           590:     fprintf(stderr, "%s -", he->key);
        !           591:     curr = bundle->conn_list->head;
        !           592:     while(curr) {
        !           593:       conn = curr->ptr;
        !           594: 
        !           595:       fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
        !           596:       curr = curr->next;
        !           597:     }
        !           598:     fprintf(stderr, "\n");
        !           599: 
        !           600:     he = Curl_hash_next_element(&iter);
        !           601:   }
        !           602: }
        !           603: #endif

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>