Annotation of embedaddon/iperf/src/iperf_util.c, revision 1.1.1.3

1.1       misho       1: /*
1.1.1.2   misho       2:  * iperf, Copyright (c) 2014, 2016, 2017, The Regents of the University of
1.1       misho       3:  * California, through Lawrence Berkeley National Laboratory (subject
                      4:  * to receipt of any required approvals from the U.S. Dept. of
                      5:  * Energy).  All rights reserved.
                      6:  *
                      7:  * If you have questions about your rights to use or distribute this
                      8:  * software, please contact Berkeley Lab's Technology Transfer
                      9:  * Department at TTD@lbl.gov.
                     10:  *
                     11:  * NOTICE.  This software is owned by the U.S. Department of Energy.
                     12:  * As such, the U.S. Government has been granted for itself and others
                     13:  * acting on its behalf a paid-up, nonexclusive, irrevocable,
                     14:  * worldwide license in the Software to reproduce, prepare derivative
                     15:  * works, and perform publicly and display publicly.  Beginning five
                     16:  * (5) years after the date permission to assert copyright is obtained
                     17:  * from the U.S. Department of Energy, and subject to any subsequent
                     18:  * five (5) year renewals, the U.S. Government is granted for itself
                     19:  * and others acting on its behalf a paid-up, nonexclusive,
                     20:  * irrevocable, worldwide license in the Software to reproduce,
                     21:  * prepare derivative works, distribute copies to the public, perform
                     22:  * publicly and display publicly, and to permit others to do so.
                     23:  *
                     24:  * This code is distributed under a BSD style license, see the LICENSE
                     25:  * file for complete information.
                     26:  */
                     27: /* iperf_util.c
                     28:  *
                     29:  * Iperf utility functions
                     30:  *
                     31:  */
                     32: #include "iperf_config.h"
                     33: 
                     34: #include <stdio.h>
1.1.1.2   misho      35: #include <signal.h>
1.1       misho      36: #include <stdlib.h>
                     37: #include <unistd.h>
                     38: #include <string.h>
                     39: #include <stdarg.h>
                     40: #include <sys/select.h>
                     41: #include <sys/types.h>
                     42: #include <sys/time.h>
                     43: #include <sys/resource.h>
                     44: #include <sys/utsname.h>
                     45: #include <time.h>
                     46: #include <errno.h>
1.1.1.2   misho      47: #include <fcntl.h>
1.1       misho      48: 
                     49: #include "cjson.h"
1.1.1.2   misho      50: #include "iperf.h"
                     51: #include "iperf_api.h"
                     52: 
                     53: /*
                     54:  * Read entropy from /dev/urandom
                     55:  * Errors are fatal.
                     56:  * Returns 0 on success.
                     57:  */
                     58: int readentropy(void *out, size_t outsize)
                     59: {
                     60:     static FILE *frandom;
                     61:     static const char rndfile[] = "/dev/urandom";
                     62: 
                     63:     if (!outsize) return 0;
                     64: 
                     65:     if (frandom == NULL) {
                     66:         frandom = fopen(rndfile, "rb");
                     67:         if (frandom == NULL) {
                     68:             iperf_errexit(NULL, "error - failed to open %s: %s\n",
                     69:                           rndfile, strerror(errno));
                     70:         }
                     71:         setbuf(frandom, NULL);
                     72:     }
                     73:     if (fread(out, 1, outsize, frandom) != outsize) {
                     74:         iperf_errexit(NULL, "error - failed to read %s: %s\n",
                     75:                       rndfile,
                     76:                       feof(frandom) ? "EOF" : strerror(errno));
                     77:     }
                     78:     return 0;
                     79: }
                     80: 
                     81: 
                     82: /*
                     83:  * Fills buffer with repeating pattern (similar to pattern that used in iperf2)
                     84:  */
                     85: void fill_with_repeating_pattern(void *out, size_t outsize)
                     86: {
                     87:     size_t i;
                     88:     int counter = 0;
                     89:     char *buf = (char *)out;
                     90: 
                     91:     if (!outsize) return;
                     92: 
                     93:     for (i = 0; i < outsize; i++) {
                     94:         buf[i] = (char)('0' + counter);
                     95:         if (counter >= 9)
                     96:             counter = 0;
                     97:         else
                     98:             counter++;
                     99:     }
                    100: }
                    101: 
1.1       misho     102: 
                    103: /* make_cookie
                    104:  *
                    105:  * Generate and return a cookie string
                    106:  *
                    107:  * Iperf uses this function to create test "cookies" which
                    108:  * server as unique test identifiers. These cookies are also
                    109:  * used for the authentication of stream connections.
1.1.1.2   misho     110:  * Assumes cookie has size (COOKIE_SIZE + 1) char's.
1.1       misho     111:  */
                    112: 
                    113: void
1.1.1.2   misho     114: make_cookie(const char *cookie)
1.1       misho     115: {
1.1.1.2   misho     116:     unsigned char *out = (unsigned char*)cookie;
                    117:     size_t pos;
                    118:     static const unsigned char rndchars[] = "abcdefghijklmnopqrstuvwxyz234567";
1.1       misho     119: 
1.1.1.2   misho     120:     readentropy(out, COOKIE_SIZE);
                    121:     for (pos = 0; pos < (COOKIE_SIZE - 1); pos++) {
                    122:         out[pos] = rndchars[out[pos] % (sizeof(rndchars) - 1)];
                    123:     }
                    124:     out[pos] = '\0';
1.1       misho     125: }
                    126: 
                    127: 
                    128: /* is_closed
                    129:  *
                    130:  * Test if the file descriptor fd is closed.
1.1.1.3 ! misho     131:  *
1.1       misho     132:  * Iperf uses this function to test whether a TCP stream socket
                    133:  * is closed, because accepting and denying an invalid connection
                    134:  * in iperf_tcp_accept is not considered an error.
                    135:  */
                    136: 
                    137: int
                    138: is_closed(int fd)
                    139: {
                    140:     struct timeval tv;
                    141:     fd_set readset;
                    142: 
                    143:     FD_ZERO(&readset);
                    144:     FD_SET(fd, &readset);
                    145:     tv.tv_sec = 0;
                    146:     tv.tv_usec = 0;
                    147: 
                    148:     if (select(fd+1, &readset, NULL, NULL, &tv) < 0) {
                    149:         if (errno == EBADF)
                    150:             return 1;
                    151:     }
                    152:     return 0;
                    153: }
                    154: 
                    155: 
                    156: double
                    157: timeval_to_double(struct timeval * tv)
                    158: {
                    159:     double d;
                    160: 
                    161:     d = tv->tv_sec + tv->tv_usec / 1000000;
                    162: 
                    163:     return d;
                    164: }
                    165: 
                    166: int
                    167: timeval_equals(struct timeval * tv0, struct timeval * tv1)
                    168: {
                    169:     if ( tv0->tv_sec == tv1->tv_sec && tv0->tv_usec == tv1->tv_usec )
                    170:        return 1;
                    171:     else
                    172:        return 0;
                    173: }
                    174: 
                    175: double
                    176: timeval_diff(struct timeval * tv0, struct timeval * tv1)
                    177: {
                    178:     double time1, time2;
1.1.1.3 ! misho     179: 
1.1       misho     180:     time1 = tv0->tv_sec + (tv0->tv_usec / 1000000.0);
                    181:     time2 = tv1->tv_sec + (tv1->tv_usec / 1000000.0);
                    182: 
                    183:     time1 = time1 - time2;
                    184:     if (time1 < 0)
                    185:         time1 = -time1;
                    186:     return time1;
                    187: }
                    188: 
                    189: void
                    190: cpu_util(double pcpu[3])
                    191: {
1.1.1.2   misho     192:     static struct iperf_time last;
1.1       misho     193:     static clock_t clast;
                    194:     static struct rusage rlast;
1.1.1.2   misho     195:     struct iperf_time now, temp_time;
1.1       misho     196:     clock_t ctemp;
                    197:     struct rusage rtemp;
                    198:     double timediff;
                    199:     double userdiff;
                    200:     double systemdiff;
                    201: 
                    202:     if (pcpu == NULL) {
1.1.1.2   misho     203:         iperf_time_now(&last);
1.1       misho     204:         clast = clock();
                    205:        getrusage(RUSAGE_SELF, &rlast);
                    206:         return;
                    207:     }
                    208: 
1.1.1.2   misho     209:     iperf_time_now(&now);
1.1       misho     210:     ctemp = clock();
                    211:     getrusage(RUSAGE_SELF, &rtemp);
                    212: 
1.1.1.2   misho     213:     iperf_time_diff(&now, &last, &temp_time);
                    214:     timediff = iperf_time_in_usecs(&temp_time);
                    215: 
1.1       misho     216:     userdiff = ((rtemp.ru_utime.tv_sec * 1000000.0 + rtemp.ru_utime.tv_usec) -
                    217:                 (rlast.ru_utime.tv_sec * 1000000.0 + rlast.ru_utime.tv_usec));
                    218:     systemdiff = ((rtemp.ru_stime.tv_sec * 1000000.0 + rtemp.ru_stime.tv_usec) -
                    219:                   (rlast.ru_stime.tv_sec * 1000000.0 + rlast.ru_stime.tv_usec));
                    220: 
                    221:     pcpu[0] = (((ctemp - clast) * 1000000.0 / CLOCKS_PER_SEC) / timediff) * 100;
                    222:     pcpu[1] = (userdiff / timediff) * 100;
                    223:     pcpu[2] = (systemdiff / timediff) * 100;
                    224: }
                    225: 
                    226: const char *
                    227: get_system_info(void)
                    228: {
                    229:     static char buf[1024];
                    230:     struct utsname  uts;
                    231: 
                    232:     memset(buf, 0, 1024);
                    233:     uname(&uts);
                    234: 
1.1.1.3 ! misho     235:     snprintf(buf, sizeof(buf), "%s %s %s %s %s", uts.sysname, uts.nodename,
1.1       misho     236:             uts.release, uts.version, uts.machine);
                    237: 
                    238:     return buf;
                    239: }
                    240: 
                    241: 
                    242: const char *
                    243: get_optional_features(void)
                    244: {
                    245:     static char features[1024];
                    246:     unsigned int numfeatures = 0;
                    247: 
                    248:     snprintf(features, sizeof(features), "Optional features available: ");
                    249: 
                    250: #if defined(HAVE_CPU_AFFINITY)
                    251:     if (numfeatures > 0) {
1.1.1.3 ! misho     252:        strncat(features, ", ",
1.1       misho     253:                sizeof(features) - strlen(features) - 1);
                    254:     }
1.1.1.3 ! misho     255:     strncat(features, "CPU affinity setting",
1.1       misho     256:        sizeof(features) - strlen(features) - 1);
                    257:     numfeatures++;
                    258: #endif /* HAVE_CPU_AFFINITY */
1.1.1.3 ! misho     259: 
1.1       misho     260: #if defined(HAVE_FLOWLABEL)
                    261:     if (numfeatures > 0) {
1.1.1.3 ! misho     262:        strncat(features, ", ",
1.1       misho     263:                sizeof(features) - strlen(features) - 1);
                    264:     }
1.1.1.3 ! misho     265:     strncat(features, "IPv6 flow label",
1.1       misho     266:        sizeof(features) - strlen(features) - 1);
                    267:     numfeatures++;
                    268: #endif /* HAVE_FLOWLABEL */
1.1.1.3 ! misho     269: 
1.1.1.2   misho     270: #if defined(HAVE_SCTP_H)
1.1       misho     271:     if (numfeatures > 0) {
1.1.1.3 ! misho     272:        strncat(features, ", ",
1.1       misho     273:                sizeof(features) - strlen(features) - 1);
                    274:     }
1.1.1.3 ! misho     275:     strncat(features, "SCTP",
1.1       misho     276:        sizeof(features) - strlen(features) - 1);
                    277:     numfeatures++;
1.1.1.2   misho     278: #endif /* HAVE_SCTP_H */
1.1.1.3 ! misho     279: 
1.1       misho     280: #if defined(HAVE_TCP_CONGESTION)
                    281:     if (numfeatures > 0) {
1.1.1.3 ! misho     282:        strncat(features, ", ",
1.1       misho     283:                sizeof(features) - strlen(features) - 1);
                    284:     }
1.1.1.3 ! misho     285:     strncat(features, "TCP congestion algorithm setting",
1.1       misho     286:        sizeof(features) - strlen(features) - 1);
                    287:     numfeatures++;
                    288: #endif /* HAVE_TCP_CONGESTION */
1.1.1.3 ! misho     289: 
1.1       misho     290: #if defined(HAVE_SENDFILE)
                    291:     if (numfeatures > 0) {
                    292:        strncat(features, ", ",
                    293:                sizeof(features) - strlen(features) - 1);
                    294:     }
                    295:     strncat(features, "sendfile / zerocopy",
                    296:        sizeof(features) - strlen(features) - 1);
                    297:     numfeatures++;
                    298: #endif /* HAVE_SENDFILE */
                    299: 
                    300: #if defined(HAVE_SO_MAX_PACING_RATE)
                    301:     if (numfeatures > 0) {
                    302:        strncat(features, ", ",
                    303:                sizeof(features) - strlen(features) - 1);
                    304:     }
                    305:     strncat(features, "socket pacing",
                    306:        sizeof(features) - strlen(features) - 1);
                    307:     numfeatures++;
                    308: #endif /* HAVE_SO_MAX_PACING_RATE */
                    309: 
1.1.1.2   misho     310: #if defined(HAVE_SSL)
                    311:     if (numfeatures > 0) {
                    312:        strncat(features, ", ",
                    313:                sizeof(features) - strlen(features) - 1);
                    314:     }
                    315:     strncat(features, "authentication",
                    316:        sizeof(features) - strlen(features) - 1);
                    317:     numfeatures++;
                    318: #endif /* HAVE_SSL */
                    319: 
1.1.1.3 ! misho     320: #if defined(HAVE_SO_BINDTODEVICE)
        !           321:     if (numfeatures > 0) {
        !           322:        strncat(features, ", ",
        !           323:                sizeof(features) - strlen(features) - 1);
        !           324:     }
        !           325:     strncat(features, "bind to device",
        !           326:        sizeof(features) - strlen(features) - 1);
        !           327:     numfeatures++;
        !           328: #endif /* HAVE_SO_BINDTODEVICE */
        !           329: 
        !           330: #if defined(HAVE_DONT_FRAGMENT)
        !           331:     if (numfeatures > 0) {
        !           332:        strncat(features, ", ",
        !           333:                sizeof(features) - strlen(features) - 1);
        !           334:     }
        !           335:     strncat(features, "support IPv4 don't fragment",
        !           336:        sizeof(features) - strlen(features) - 1);
        !           337:     numfeatures++;
        !           338: #endif /* HAVE_DONT_FRAGMENT */
        !           339: 
1.1       misho     340:     if (numfeatures == 0) {
1.1.1.3 ! misho     341:        strncat(features, "None",
1.1       misho     342:                sizeof(features) - strlen(features) - 1);
                    343:     }
                    344: 
                    345:     return features;
                    346: }
                    347: 
                    348: /* Helper routine for building cJSON objects in a printf-like manner.
                    349: **
                    350: ** Sample call:
                    351: **   j = iperf_json_printf("foo: %b  bar: %d  bletch: %f  eep: %s", b, i, f, s);
                    352: **
                    353: ** The four formatting characters and the types they expect are:
                    354: **   %b  boolean           int
                    355: **   %d  integer           int64_t
                    356: **   %f  floating point    double
                    357: **   %s  string            char *
                    358: ** If the values you're passing in are not these exact types, you must
                    359: ** cast them, there is no automatic type coercion/widening here.
                    360: **
                    361: ** The colons mark the end of field names, and blanks are ignored.
                    362: **
                    363: ** This routine is not particularly robust, but it's not part of the API,
                    364: ** it's just for internal iperf3 use.
                    365: */
                    366: cJSON*
                    367: iperf_json_printf(const char *format, ...)
                    368: {
                    369:     cJSON* o;
                    370:     va_list argp;
                    371:     const char *cp;
                    372:     char name[100];
                    373:     char* np;
                    374:     cJSON* j;
                    375: 
                    376:     o = cJSON_CreateObject();
                    377:     if (o == NULL)
                    378:         return NULL;
                    379:     va_start(argp, format);
                    380:     np = name;
                    381:     for (cp = format; *cp != '\0'; ++cp) {
                    382:        switch (*cp) {
                    383:            case ' ':
                    384:            break;
                    385:            case ':':
                    386:            *np = '\0';
                    387:            break;
                    388:            case '%':
                    389:            ++cp;
                    390:            switch (*cp) {
                    391:                case 'b':
                    392:                j = cJSON_CreateBool(va_arg(argp, int));
                    393:                break;
                    394:                case 'd':
                    395:                j = cJSON_CreateNumber(va_arg(argp, int64_t));
                    396:                break;
                    397:                case 'f':
                    398:                j = cJSON_CreateNumber(va_arg(argp, double));
                    399:                break;
                    400:                case 's':
                    401:                j = cJSON_CreateString(va_arg(argp, char *));
                    402:                break;
                    403:                default:
                    404:                va_end(argp);
                    405:                return NULL;
                    406:            }
                    407:            if (j == NULL) {
                    408:                va_end(argp);
                    409:                return NULL;
                    410:            }
                    411:            cJSON_AddItemToObject(o, name, j);
                    412:            np = name;
                    413:            break;
                    414:            default:
                    415:            *np++ = *cp;
                    416:            break;
                    417:        }
                    418:     }
                    419:     va_end(argp);
                    420:     return o;
                    421: }
                    422: 
                    423: /* Debugging routine to dump out an fd_set. */
                    424: void
1.1.1.2   misho     425: iperf_dump_fdset(FILE *fp, const char *str, int nfds, fd_set *fds)
1.1       misho     426: {
                    427:     int fd;
                    428:     int comma;
                    429: 
                    430:     fprintf(fp, "%s: [", str);
                    431:     comma = 0;
                    432:     for (fd = 0; fd < nfds; ++fd) {
                    433:         if (FD_ISSET(fd, fds)) {
                    434:            if (comma)
                    435:                fprintf(fp, ", ");
                    436:            fprintf(fp, "%d", fd);
                    437:            comma = 1;
                    438:        }
                    439:     }
                    440:     fprintf(fp, "]\n");
                    441: }
1.1.1.2   misho     442: 
                    443: /*
                    444:  * daemon(3) implementation for systems lacking one.
                    445:  * Cobbled together from various daemon(3) implementations,
                    446:  * not intended to be general-purpose. */
                    447: #ifndef HAVE_DAEMON
                    448: int daemon(int nochdir, int noclose)
                    449: {
                    450:     pid_t pid = 0;
                    451:     pid_t sid = 0;
                    452:     int fd;
                    453: 
                    454:     /*
                    455:      * Ignore any possible SIGHUP when the parent process exits.
                    456:      * Note that the iperf3 server process will eventually install
                    457:      * its own signal handler for SIGHUP, so we can be a little
                    458:      * sloppy about not restoring the prior value.  This does not
                    459:      * generalize.
                    460:      */
                    461:     signal(SIGHUP, SIG_IGN);
                    462: 
                    463:     pid = fork();
                    464:     if (pid < 0) {
                    465:            return -1;
                    466:     }
                    467:     if (pid > 0) {
                    468:        /* Use _exit() to avoid doing atexit() stuff. */
                    469:        _exit(0);
                    470:     }
                    471: 
                    472:     sid = setsid();
                    473:     if (sid < 0) {
                    474:        return -1;
                    475:     }
                    476: 
                    477:     /*
                    478:      * Fork again to avoid becoming a session leader.
1.1.1.3 ! misho     479:      * This might only matter on old SVr4-derived OSs.
        !           480:      * Note in particular that glibc and FreeBSD libc
1.1.1.2   misho     481:      * only fork once.
                    482:      */
                    483:     pid = fork();
                    484:     if (pid == -1) {
                    485:        return -1;
                    486:     } else if (pid != 0) {
                    487:        _exit(0);
                    488:     }
                    489: 
                    490:     if (!nochdir) {
                    491:        chdir("/");
                    492:     }
                    493: 
                    494:     if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
                    495:        dup2(fd, STDIN_FILENO);
                    496:        dup2(fd, STDOUT_FILENO);
                    497:        dup2(fd, STDERR_FILENO);
                    498:        if (fd > 2) {
                    499:            close(fd);
                    500:        }
                    501:     }
                    502:     return (0);
                    503: }
                    504: #endif /* HAVE_DAEMON */
                    505: 
                    506: /* Compatibility version of getline(3) for systems that don't have it.. */
                    507: #ifndef HAVE_GETLINE
                    508: /* The following code adopted from NetBSD's getline.c, which is: */
                    509: 
                    510: /*-
                    511:  * Copyright (c) 2011 The NetBSD Foundation, Inc.
                    512:  * All rights reserved.
                    513:  *
                    514:  * This code is derived from software contributed to The NetBSD Foundation
                    515:  * by Christos Zoulas.
                    516:  *
                    517:  * Redistribution and use in source and binary forms, with or without
                    518:  * modification, are permitted provided that the following conditions
                    519:  * are met:
                    520:  * 1. Redistributions of source code must retain the above copyright
                    521:  *    notice, this list of conditions and the following disclaimer.
                    522:  * 2. Redistributions in binary form must reproduce the above copyright
                    523:  *    notice, this list of conditions and the following disclaimer in the
                    524:  *    documentation and/or other materials provided with the distribution.
                    525:  *
                    526:  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
                    527:  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
                    528:  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
                    529:  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
                    530:  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
                    531:  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
                    532:  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
                    533:  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
                    534:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
                    535:  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
                    536:  * POSSIBILITY OF SUCH DAMAGE.
                    537:  */
                    538: ssize_t
                    539: getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
                    540: {
                    541:        char *ptr, *eptr;
                    542: 
                    543: 
                    544:        if (*buf == NULL || *bufsiz == 0) {
                    545:                *bufsiz = BUFSIZ;
                    546:                if ((*buf = malloc(*bufsiz)) == NULL)
                    547:                        return -1;
                    548:        }
                    549: 
                    550:        for (ptr = *buf, eptr = *buf + *bufsiz;;) {
                    551:                int c = fgetc(fp);
                    552:                if (c == -1) {
                    553:                        if (feof(fp)) {
                    554:                                ssize_t diff = (ssize_t)(ptr - *buf);
                    555:                                if (diff != 0) {
                    556:                                        *ptr = '\0';
                    557:                                        return diff;
                    558:                                }
                    559:                        }
                    560:                        return -1;
                    561:                }
                    562:                *ptr++ = c;
                    563:                if (c == delimiter) {
                    564:                        *ptr = '\0';
                    565:                        return ptr - *buf;
                    566:                }
                    567:                if (ptr + 2 >= eptr) {
                    568:                        char *nbuf;
                    569:                        size_t nbufsiz = *bufsiz * 2;
                    570:                        ssize_t d = ptr - *buf;
                    571:                        if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
                    572:                                return -1;
                    573:                        *buf = nbuf;
                    574:                        *bufsiz = nbufsiz;
                    575:                        eptr = nbuf + nbufsiz;
                    576:                        ptr = nbuf + d;
                    577:                }
                    578:        }
                    579: }
                    580: 
                    581: ssize_t
                    582: getline(char **buf, size_t *bufsiz, FILE *fp)
                    583: {
                    584:        return getdelim(buf, bufsiz, '\n', fp);
                    585: }
                    586: 
                    587: #endif

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