[BACK]Return to nfs_boot.c CVS log [TXT][DIR] Up to [local] / sys / nfs

Annotation of sys/nfs/nfs_boot.c, Revision 1.1.1.1

1.1       nbrk        1: /*     $OpenBSD: nfs_boot.c,v 1.18 2006/06/17 14:14:12 henning Exp $ */
                      2: /*     $NetBSD: nfs_boot.c,v 1.26 1996/05/07 02:51:25 thorpej Exp $    */
                      3:
                      4: /*
                      5:  * Copyright (c) 1995 Adam Glass, Gordon Ross
                      6:  * All rights reserved.
                      7:  *
                      8:  * Redistribution and use in source and binary forms, with or without
                      9:  * modification, are permitted provided that the following conditions
                     10:  * are met:
                     11:  * 1. Redistributions of source code must retain the above copyright
                     12:  *    notice, this list of conditions and the following disclaimer.
                     13:  * 2. Redistributions in binary form must reproduce the above copyright
                     14:  *    notice, this list of conditions and the following disclaimer in the
                     15:  *    documentation and/or other materials provided with the distribution.
                     16:  * 3. The name of the authors may not be used to endorse or promote products
                     17:  *    derived from this software without specific prior written permission.
                     18:  *
                     19:  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
                     20:  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
                     21:  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
                     22:  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
                     23:  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
                     24:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
                     25:  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
                     26:  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
                     27:  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
                     28:  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                     29:  */
                     30:
                     31: #include <sys/param.h>
                     32: #include <sys/systm.h>
                     33: #include <sys/kernel.h>
                     34: #include <sys/conf.h>
                     35: #include <sys/ioctl.h>
                     36: #include <sys/proc.h>
                     37: #include <sys/mount.h>
                     38: #include <sys/mbuf.h>
                     39: #include <sys/reboot.h>
                     40: #include <sys/socket.h>
                     41: #include <sys/socketvar.h>
                     42:
                     43: #include <net/if.h>
                     44: #include <net/route.h>
                     45:
                     46: #include <netinet/in.h>
                     47: #include <netinet/if_ether.h>
                     48:
                     49: #include <nfs/rpcv2.h>
                     50: #include <nfs/nfsproto.h>
                     51: #include <nfs/nfs.h>
                     52: #include <nfs/nfsdiskless.h>
                     53: #include <nfs/krpc.h>
                     54: #include <nfs/xdr_subs.h>
                     55: #include <nfs/nfs_var.h>
                     56:
                     57: #include "ether.h"
                     58:
                     59: #if !defined(NFSCLIENT) || (NETHER == 0 && NFDDI == 0)
                     60:
                     61: int
                     62: nfs_boot_init(nd, procp)
                     63:        struct nfs_diskless *nd;
                     64:        struct proc *procp;
                     65: {
                     66:        panic("nfs_boot_init: NFSCLIENT not enabled in kernel");
                     67: }
                     68:
                     69: int
                     70: nfs_boot_getfh(bpsin, key, ndmntp, retries)
                     71:        struct sockaddr_in *bpsin;
                     72:        char *key;
                     73:        struct nfs_dlmount *ndmntp;
                     74:        int retries;
                     75: {
                     76:        /* can not get here */
                     77:        return (EOPNOTSUPP);
                     78: }
                     79:
                     80: #else
                     81:
                     82: /*
                     83:  * Support for NFS diskless booting, specifically getting information
                     84:  * about where to boot from, what pathnames, etc.
                     85:  *
                     86:  * This implementation uses RARP and the bootparam RPC.
                     87:  * We are forced to implement RPC anyway (to get file handles)
                     88:  * so we might as well take advantage of it for bootparam too.
                     89:  *
                     90:  * The diskless boot sequence goes as follows:
                     91:  * (1) Use RARP to get our interface address
                     92:  * (2) Use RPC/bootparam/whoami to get our hostname,
                     93:  *     our IP address, and the server's IP address.
                     94:  * (3) Use RPC/bootparam/getfile to get the root path
                     95:  * (4) Use RPC/mountd to get the root file handle
                     96:  * (5) Use RPC/bootparam/getfile to get the swap path
                     97:  * (6) Use RPC/mountd to get the swap file handle
                     98:  *
                     99:  * (This happens to be the way Sun does it too.)
                    100:  */
                    101:
                    102: /* bootparam RPC */
                    103: static int bp_whoami(struct sockaddr_in *bpsin,
                    104:        struct in_addr *my_ip, struct in_addr *gw_ip);
                    105: static int bp_getfile(struct sockaddr_in *bpsin, char *key,
                    106:        struct sockaddr_in *mdsin, char *servname, char *path, int retries);
                    107:
                    108: /* mountd RPC */
                    109: static int md_mount(struct sockaddr_in *mdsin, char *path,
                    110:        u_char *fh);
                    111:
                    112: char   *nfsbootdevname;
                    113:
                    114: /*
                    115:  * Called with an empty nfs_diskless struct to be filled in.
                    116:  */
                    117: int
                    118: nfs_boot_init(nd, procp)
                    119:        struct nfs_diskless *nd;
                    120:        struct proc *procp;
                    121: {
                    122:        struct ifreq ireq;
                    123:        struct in_addr my_ip, gw_ip;
                    124:        struct sockaddr_in bp_sin;
                    125:        struct sockaddr_in *sin;
                    126:        struct ifnet *ifp;
                    127:        struct socket *so;
                    128:        int error;
                    129:
                    130:        /*
                    131:         * Find an interface, rarp for its ip address, stuff it, the
                    132:         * implied broadcast addr, and netmask into a nfs_diskless struct.
                    133:         *
                    134:         * This was moved here from nfs_vfsops.c because this procedure
                    135:         * would be quite different if someone decides to write (i.e.) a
                    136:         * BOOTP version of this file (might not use RARP, etc.)
                    137:         */
                    138:
                    139:        /*
                    140:         * Find a network interface.
                    141:         */
                    142:        if (nfsbootdevname)
                    143:                ifp = ifunit(nfsbootdevname);
                    144:        else {
                    145:                for (ifp = TAILQ_FIRST(&ifnet); ifp != NULL;
                    146:                    ifp = TAILQ_NEXT(ifp, if_list)) {
                    147:                        if ((ifp->if_flags &
                    148:                             (IFF_LOOPBACK|IFF_POINTOPOINT)) == 0)
                    149:                                break;
                    150:                }
                    151:        }
                    152:        if (ifp == NULL)
                    153:                panic("nfs_boot: no suitable interface");
                    154:        bcopy(ifp->if_xname, ireq.ifr_name, IFNAMSIZ);
                    155:        printf("nfs_boot: using interface %s, with revarp & bootparams\n",
                    156:            ireq.ifr_name);
                    157:
                    158:        /*
                    159:         * Bring up the interface.
                    160:         *
                    161:         * Get the old interface flags and or IFF_UP into them; if
                    162:         * IFF_UP set blindly, interface selection can be clobbered.
                    163:         */
                    164:        if ((error = socreate(AF_INET, &so, SOCK_DGRAM, 0)) != 0)
                    165:                panic("nfs_boot: socreate, error=%d", error);
                    166:        error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)&ireq, procp);
                    167:        if (error)
                    168:                panic("nfs_boot: GIFFLAGS, error=%d", error);
                    169:        ireq.ifr_flags |= IFF_UP;
                    170:        error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ireq, procp);
                    171:        if (error)
                    172:                panic("nfs_boot: SIFFLAGS, error=%d", error);
                    173:
                    174:        /*
                    175:         * Do RARP for the interface address.
                    176:         */
                    177:        if ((error = revarpwhoami(&my_ip, ifp)) != 0)
                    178:                panic("revarp failed, error=%d", error);
                    179:        printf("nfs_boot: client_addr=%s\n", inet_ntoa(my_ip));
                    180:
                    181:        /*
                    182:         * Do enough of ifconfig(8) so that the chosen interface
                    183:         * can talk to the servers.  (just set the address)
                    184:         */
                    185:        sin = (struct sockaddr_in *)&ireq.ifr_addr;
                    186:        bzero((caddr_t)sin, sizeof(*sin));
                    187:        sin->sin_len = sizeof(*sin);
                    188:        sin->sin_family = AF_INET;
                    189:        sin->sin_addr.s_addr = my_ip.s_addr;
                    190:        error = ifioctl(so, SIOCSIFADDR, (caddr_t)&ireq, procp);
                    191:        if (error)
                    192:                panic("nfs_boot: set if addr, error=%d", error);
                    193:
                    194:        soclose(so);
                    195:
                    196:        /*
                    197:         * Get client name and gateway address.
                    198:         * RPC: bootparam/whoami
                    199:         * Use the old broadcast address for the WHOAMI
                    200:         * call because we do not yet know our netmask.
                    201:         * The server address returned by the WHOAMI call
                    202:         * is used for all subsequent booptaram RPCs.
                    203:         */
                    204:        bzero((caddr_t)&bp_sin, sizeof(bp_sin));
                    205:        bp_sin.sin_len = sizeof(bp_sin);
                    206:        bp_sin.sin_family = AF_INET;
                    207:        bp_sin.sin_addr.s_addr = INADDR_BROADCAST;
                    208:        hostnamelen = MAXHOSTNAMELEN;
                    209:
                    210:        /* this returns gateway IP address */
                    211:        error = bp_whoami(&bp_sin, &my_ip, &gw_ip);
                    212:        if (error)
                    213:                panic("nfs_boot: bootparam whoami, error=%d", error);
                    214:        printf("nfs_boot: server_addr=%s hostname=%s\n",
                    215:            inet_ntoa(bp_sin.sin_addr), hostname);
                    216:
                    217: #ifdef NFS_BOOT_GATEWAY
                    218:        /*
                    219:         * XXX - This code is conditionally compiled only because
                    220:         * many bootparam servers (in particular, SunOS 4.1.3)
                    221:         * always set the gateway address to their own address.
                    222:         * The bootparam server is not necessarily the gateway.
                    223:         * We could just believe the server, and at worst you would
                    224:         * need to delete the incorrect default route before adding
                    225:         * the correct one, but for simplicity, ignore the gateway.
                    226:         * If your server is OK, you can turn on this option.
                    227:         *
                    228:         * If the gateway address is set, add a default route.
                    229:         * (The mountd RPCs may go across a gateway.)
                    230:         */
                    231:        if (gw_ip.s_addr) {
                    232:                struct sockaddr dst, gw, mask;
                    233:                /* Destination: (default) */
                    234:                bzero((caddr_t)&dst, sizeof(dst));
                    235:                dst.sa_len = sizeof(dst);
                    236:                dst.sa_family = AF_INET;
                    237:                /* Gateway: */
                    238:                bzero((caddr_t)&gw, sizeof(gw));
                    239:                sin = (struct sockaddr_in *)&gw;
                    240:                sin->sin_len = sizeof(gw);
                    241:                sin->sin_family = AF_INET;
                    242:                sin->sin_addr.s_addr = gw_ip.s_addr;
                    243:                /* Mask: (zero length) */
                    244:                bzero(&mask, sizeof(mask));
                    245:
                    246:                printf("nfs_boot: gateway=%s\n", inet_ntoa(gw_ip));
                    247:                /* add, dest, gw, mask, flags, 0 */
                    248:                error = rtrequest(RTM_ADD, &dst, (struct sockaddr *)&gw,
                    249:                    &mask, (RTF_UP | RTF_GATEWAY | RTF_STATIC), NULL, 0);
                    250:                if (error)
                    251:                        printf("nfs_boot: add route, error=%d\n", error);
                    252:        }
                    253: #endif
                    254:
                    255:        bcopy(&bp_sin, &nd->nd_boot, sizeof(bp_sin));
                    256:
                    257:        return (0);
                    258: }
                    259:
                    260: int
                    261: nfs_boot_getfh(bpsin, key, ndmntp, retries)
                    262:        struct sockaddr_in *bpsin;      /* bootparam server */
                    263:        char *key;                      /* root or swap */
                    264:        struct nfs_dlmount *ndmntp;     /* output */
                    265:        int retries;
                    266: {
                    267:        char pathname[MAXPATHLEN];
                    268:        char *sp, *dp, *endp;
                    269:        struct sockaddr_in *sin;
                    270:        int error;
                    271:
                    272:        sin = &ndmntp->ndm_saddr;
                    273:
                    274:        /*
                    275:         * Get server:pathname for "key" (root or swap)
                    276:         * using RPC to bootparam/getfile
                    277:         */
                    278:        error = bp_getfile(bpsin, key, sin, ndmntp->ndm_host, pathname,
                    279:            retries);
                    280:        if (error) {
                    281:                printf("nfs_boot: bootparam get %s: %d\n", key, error);
                    282:                return (error);
                    283:        }
                    284:
                    285:        /*
                    286:         * Get file handle for "key" (root or swap)
                    287:         * using RPC to mountd/mount
                    288:         */
                    289:        error = md_mount(sin, pathname, ndmntp->ndm_fh);
                    290:        if (error) {
                    291:                printf("nfs_boot: mountd %s, error=%d\n", key, error);
                    292:                return (error);
                    293:        }
                    294:
                    295:        /* Set port number for NFS use. */
                    296:        /* XXX: NFS port is always 2049, right? */
                    297:        error = krpc_portmap(sin, NFS_PROG, NFS_VER2, &sin->sin_port);
                    298:        if (error) {
                    299:                printf("nfs_boot: portmap NFS/v2, error=%d\n", error);
                    300:                return (error);
                    301:        }
                    302:
                    303:        /* Construct remote path (for getmntinfo(3)) */
                    304:        dp = ndmntp->ndm_host;
                    305:        endp = dp + MNAMELEN - 1;
                    306:        dp += strlen(dp);
                    307:        *dp++ = ':';
                    308:        for (sp = pathname; *sp && dp < endp;)
                    309:                *dp++ = *sp++;
                    310:        *dp = '\0';
                    311:
                    312:        return (0);
                    313: }
                    314:
                    315:
                    316: /*
                    317:  * RPC: bootparam/whoami
                    318:  * Given client IP address, get:
                    319:  *     client name     (hostname)
                    320:  *     domain name (domainname)
                    321:  *     gateway address
                    322:  *
                    323:  * The hostname and domainname are set here for convenience.
                    324:  *
                    325:  * Note - bpsin is initialized to the broadcast address,
                    326:  * and will be replaced with the bootparam server address
                    327:  * after this call is complete.  Have to use PMAP_PROC_CALL
                    328:  * to make sure we get responses only from a servers that
                    329:  * know about us (don't want to broadcast a getport call).
                    330:  */
                    331: static int
                    332: bp_whoami(bpsin, my_ip, gw_ip)
                    333:        struct sockaddr_in *bpsin;
                    334:        struct in_addr *my_ip;
                    335:        struct in_addr *gw_ip;
                    336: {
                    337:        /* RPC structures for PMAPPROC_CALLIT */
                    338:        struct whoami_call {
                    339:                u_int32_t call_prog;
                    340:                u_int32_t call_vers;
                    341:                u_int32_t call_proc;
                    342:                u_int32_t call_arglen;
                    343:        } *call;
                    344:        struct callit_reply {
                    345:                u_int32_t port;
                    346:                u_int32_t encap_len;
                    347:                /* encapsulated data here */
                    348:        } *reply;
                    349:
                    350:        struct mbuf *m, *from;
                    351:        struct sockaddr_in *sin;
                    352:        int error, msg_len;
                    353:        int16_t port;
                    354:
                    355:        /*
                    356:         * Build request message for PMAPPROC_CALLIT.
                    357:         */
                    358:        m = m_get(M_WAIT, MT_DATA);
                    359:        call = mtod(m, struct whoami_call *);
                    360:        m->m_len = sizeof(*call);
                    361:        call->call_prog = txdr_unsigned(BOOTPARAM_PROG);
                    362:        call->call_vers = txdr_unsigned(BOOTPARAM_VERS);
                    363:        call->call_proc = txdr_unsigned(BOOTPARAM_WHOAMI);
                    364:
                    365:        /*
                    366:         * append encapsulated data (client IP address)
                    367:         */
                    368:        m->m_next = xdr_inaddr_encode(my_ip);
                    369:        call->call_arglen = txdr_unsigned(m->m_next->m_len);
                    370:
                    371:        /* RPC: portmap/callit */
                    372:        bpsin->sin_port = htons(PMAPPORT);
                    373:        from = NULL;
                    374:        error = krpc_call(bpsin, PMAPPROG, PMAPVERS,
                    375:                        PMAPPROC_CALLIT, &m, &from, -1);
                    376:        if (error)
                    377:                return error;
                    378:
                    379:        /*
                    380:         * Parse result message.
                    381:         */
                    382:        if (m->m_len < sizeof(*reply)) {
                    383:                m = m_pullup(m, sizeof(*reply));
                    384:                if (m == NULL)
                    385:                        goto bad;
                    386:        }
                    387:        reply = mtod(m, struct callit_reply *);
                    388:        port = fxdr_unsigned(u_int32_t, reply->port);
                    389:        msg_len = fxdr_unsigned(u_int32_t, reply->encap_len);
                    390:        m_adj(m, sizeof(*reply));
                    391:
                    392:        /*
                    393:         * Save bootparam server address
                    394:         */
                    395:        sin = mtod(from, struct sockaddr_in *);
                    396:        bpsin->sin_port = htons(port);
                    397:        bpsin->sin_addr.s_addr = sin->sin_addr.s_addr;
                    398:
                    399:        /* client name */
                    400:        hostnamelen = MAXHOSTNAMELEN-1;
                    401:        m = xdr_string_decode(m, hostname, &hostnamelen);
                    402:        if (m == NULL)
                    403:                goto bad;
                    404:
                    405:        /* domain name */
                    406:        domainnamelen = MAXHOSTNAMELEN-1;
                    407:        m = xdr_string_decode(m, domainname, &domainnamelen);
                    408:        if (m == NULL)
                    409:                goto bad;
                    410:
                    411:        /* gateway address */
                    412:        m = xdr_inaddr_decode(m, gw_ip);
                    413:        if (m == NULL)
                    414:                goto bad;
                    415:
                    416:        /* success */
                    417:        goto out;
                    418:
                    419: bad:
                    420:        printf("nfs_boot: bootparam_whoami: bad reply\n");
                    421:        error = EBADRPC;
                    422:
                    423: out:
                    424:        if (from)
                    425:                m_freem(from);
                    426:        if (m)
                    427:                m_freem(m);
                    428:        return(error);
                    429: }
                    430:
                    431:
                    432: /*
                    433:  * RPC: bootparam/getfile
                    434:  * Given client name and file "key", get:
                    435:  *     server name
                    436:  *     server IP address
                    437:  *     server pathname
                    438:  */
                    439: static int
                    440: bp_getfile(bpsin, key, md_sin, serv_name, pathname, retries)
                    441:        struct sockaddr_in *bpsin;
                    442:        char *key;
                    443:        struct sockaddr_in *md_sin;
                    444:        char *serv_name;
                    445:        char *pathname;
                    446:        int retries;
                    447: {
                    448:        struct mbuf *m;
                    449:        struct sockaddr_in *sin;
                    450:        struct in_addr inaddr;
                    451:        int error, sn_len, path_len;
                    452:
                    453:        /*
                    454:         * Build request message.
                    455:         */
                    456:
                    457:        /* client name (hostname) */
                    458:        m  = xdr_string_encode(hostname, hostnamelen);
                    459:        if (m == NULL)
                    460:                return (ENOMEM);
                    461:
                    462:        /* key name (root or swap) */
                    463:        m->m_next = xdr_string_encode(key, strlen(key));
                    464:        if (m->m_next == NULL)
                    465:                return (ENOMEM);
                    466:
                    467:        /* RPC: bootparam/getfile */
                    468:        error = krpc_call(bpsin, BOOTPARAM_PROG, BOOTPARAM_VERS,
                    469:                        BOOTPARAM_GETFILE, &m, NULL, retries);
                    470:        if (error)
                    471:                return error;
                    472:
                    473:        /*
                    474:         * Parse result message.
                    475:         */
                    476:
                    477:        /* server name */
                    478:        sn_len = MNAMELEN-1;
                    479:        m = xdr_string_decode(m, serv_name, &sn_len);
                    480:        if (m == NULL)
                    481:                goto bad;
                    482:
                    483:        /* server IP address (mountd/NFS) */
                    484:        m = xdr_inaddr_decode(m, &inaddr);
                    485:        if (m == NULL)
                    486:                goto bad;
                    487:
                    488:        /* server pathname */
                    489:        path_len = MAXPATHLEN-1;
                    490:        m = xdr_string_decode(m, pathname, &path_len);
                    491:        if (m == NULL)
                    492:                goto bad;
                    493:
                    494:        /* setup server socket address */
                    495:        sin = md_sin;
                    496:        bzero((caddr_t)sin, sizeof(*sin));
                    497:        sin->sin_len = sizeof(*sin);
                    498:        sin->sin_family = AF_INET;
                    499:        sin->sin_addr = inaddr;
                    500:
                    501:        /* success */
                    502:        goto out;
                    503:
                    504: bad:
                    505:        printf("nfs_boot: bootparam_getfile: bad reply\n");
                    506:        error = EBADRPC;
                    507:
                    508: out:
                    509:        m_freem(m);
                    510:        return(0);
                    511: }
                    512:
                    513:
                    514: /*
                    515:  * RPC: mountd/mount
                    516:  * Given a server pathname, get an NFS file handle.
                    517:  * Also, sets sin->sin_port to the NFS service port.
                    518:  */
                    519: static int
                    520: md_mount(mdsin, path, fhp)
                    521:        struct sockaddr_in *mdsin;              /* mountd server address */
                    522:        char *path;
                    523:        u_char *fhp;
                    524: {
                    525:        /* The RPC structures */
                    526:        struct rdata {
                    527:                u_int32_t errno;
                    528:                u_int8_t  fh[NFSX_V2FH];
                    529:        } *rdata;
                    530:        struct mbuf *m;
                    531:        int error;
                    532:
                    533:        /* Get port number for MOUNTD. */
                    534:        error = krpc_portmap(mdsin, RPCPROG_MNT, RPCMNT_VER1,
                    535:                                                 &mdsin->sin_port);
                    536:        if (error) return error;
                    537:
                    538:        m = xdr_string_encode(path, strlen(path));
                    539:        if (m == NULL)
                    540:                return ENOMEM;
                    541:
                    542:        /* Do RPC to mountd. */
                    543:        error = krpc_call(mdsin, RPCPROG_MNT, RPCMNT_VER1,
                    544:                        RPCMNT_MOUNT, &m, NULL, -1);
                    545:        if (error)
                    546:                return error;   /* message already freed */
                    547:
                    548:        /* The reply might have only the errno. */
                    549:        if (m->m_len < 4)
                    550:                goto bad;
                    551:        /* Have at least errno, so check that. */
                    552:        rdata = mtod(m, struct rdata *);
                    553:        error = fxdr_unsigned(u_int32_t, rdata->errno);
                    554:        if (error)
                    555:                goto out;
                    556:
                    557:         /* Have errno==0, so the fh must be there. */
                    558:        if (m->m_len < sizeof(*rdata)) {
                    559:                m = m_pullup(m, sizeof(*rdata));
                    560:                if (m == NULL)
                    561:                        goto bad;
                    562:                rdata = mtod(m, struct rdata *);
                    563:        }
                    564:        bcopy(rdata->fh, fhp, NFSX_V2FH);
                    565:        goto out;
                    566:
                    567: bad:
                    568:        error = EBADRPC;
                    569:
                    570: out:
                    571:        m_freem(m);
                    572:        return error;
                    573: }
                    574:
                    575: #endif /* ifdef NFSCLIENT */

CVSweb