[BACK]Return to pxe.c CVS log [TXT][DIR] Up to [local] / sys / arch / i386 / stand / libsa

Annotation of sys/arch/i386/stand/libsa/pxe.c, Revision 1.1.1.1

1.1       nbrk        1: /*     $OpenBSD: pxe.c,v 1.5 2007/07/27 17:46:56 tom Exp $ */
                      2: /*     $NetBSD: pxe.c,v 1.5 2003/03/11 18:29:00 drochner Exp $ */
                      3:
                      4: /*
                      5:  * Copyright 2001 Wasabi Systems, Inc.
                      6:  * All rights reserved.
                      7:  *
                      8:  * Written by Jason R. Thorpe for Wasabi Systems, Inc.
                      9:  *
                     10:  * Redistribution and use in source and binary forms, with or without
                     11:  * modification, are permitted provided that the following conditions
                     12:  * are met:
                     13:  * 1. Redistributions of source code must retain the above copyright
                     14:  *    notice, this list of conditions and the following disclaimer.
                     15:  * 2. Redistributions in binary form must reproduce the above copyright
                     16:  *    notice, this list of conditions and the following disclaimer in the
                     17:  *    documentation and/or other materials provided with the distribution.
                     18:  * 3. All advertising materials mentioning features or use of this software
                     19:  *    must display the following acknowledgement:
                     20:  *     This product includes software developed for the NetBSD Project by
                     21:  *     Wasabi Systems, Inc.
                     22:  * 4. The name of Wasabi Systems, Inc. may not be used to endorse
                     23:  *    or promote products derived from this software without specific prior
                     24:  *    written permission.
                     25:  *
                     26:  * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
                     27:  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
                     28:  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
                     29:  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL WASABI SYSTEMS, INC
                     30:  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
                     31:  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
                     32:  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
                     33:  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
                     34:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
                     35:  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
                     36:  * POSSIBILITY OF SUCH DAMAGE.
                     37:  */
                     38:
                     39: /*
                     40:  * Copyright (c) 2000 Alfred Perlstein <alfred@freebsd.org>
                     41:  * All rights reserved.
                     42:  * Copyright (c) 2000 Paul Saab <ps@freebsd.org>
                     43:  * All rights reserved.
                     44:  * Copyright (c) 2000 John Baldwin <jhb@freebsd.org>
                     45:  * All rights reserved.
                     46:  *
                     47:  * Redistribution and use in source and binary forms, with or without
                     48:  * modification, are permitted provided that the following conditions
                     49:  * are met:
                     50:  * 1. Redistributions of source code must retain the above copyright
                     51:  *    notice, this list of conditions and the following disclaimer.
                     52:  * 2. Redistributions in binary form must reproduce the above copyright
                     53:  *    notice, this list of conditions and the following disclaimer in the
                     54:  *    documentation and/or other materials provided with the distribution.
                     55:  *
                     56:  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
                     57:  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
                     58:  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
                     59:  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
                     60:  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
                     61:  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
                     62:  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
                     63:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
                     64:  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
                     65:  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
                     66:  * SUCH DAMAGE.
                     67:  */
                     68:
                     69: /*
                     70:  * Support for the Intel Preboot Execution Environment (PXE).
                     71:  *
                     72:  * PXE provides a UDP implementation as well as a UNDI network device
                     73:  * driver.  UNDI is much more complicated to use than PXE UDP, so we
                     74:  * use PXE UDP as a cheap and easy way to get PXE support.
                     75:  */
                     76:
                     77: #include <sys/param.h>
                     78: #include <sys/socket.h>
                     79:
                     80: #ifdef _STANDALONE
                     81: #include <lib/libkern/libkern.h>
                     82: #else
                     83: #include <string.h>
                     84: #endif
                     85:
                     86: #include <net/if.h>
                     87:
                     88: #include <netinet/in.h>
                     89: #include <netinet/if_ether.h>
                     90: #include <netinet/in_systm.h>
                     91: #include <netinet/ip.h>
                     92: #include <netinet/ip_var.h>
                     93: #include <netinet/udp.h>
                     94: #include <netinet/udp_var.h>
                     95:
                     96: #include <lib/libsa/stand.h>
                     97: #include <lib/libsa/net.h>
                     98: #include <lib/libsa/bootp.h>
                     99:
                    100: #include <stand/boot/bootarg.h>
                    101: #include <machine/biosvar.h>
                    102:
                    103: #include "pxeboot.h"
                    104: #include "pxe.h"
                    105: #include "pxe_netif.h"
                    106:
                    107: void   (*pxe_call)(u_int16_t);
                    108:
                    109: void   pxecall_bangpxe(u_int16_t);     /* pxe_call.S */
                    110: void   pxecall_pxenv(u_int16_t);       /* pxe_call.S */
                    111:
                    112: char pxe_command_buf[256];
                    113:
                    114: BOOTPLAYER bootplayer;
                    115:
                    116: struct in_addr servip;                 /* for tftp */  /* XXX init this */
                    117:
                    118: extern char *bootmac;                  /* To pass to kernel */
                    119:
                    120: /* static struct btinfo_netif bi_netif; */
                    121:
                    122: /*****************************************************************************
                    123:  * This section is a replacement for libsa/udp.c
                    124:  *****************************************************************************/
                    125:
                    126: /* Caller must leave room for ethernet, ip, and udp headers in front!! */
                    127: ssize_t
                    128: pxesendudp(struct iodesc *d, void *pkt, size_t len)
                    129: {
                    130:        t_PXENV_UDP_WRITE *uw = (void *) pxe_command_buf;
                    131:
                    132:        uw->status = 0;
                    133:
                    134:        uw->ip = d->destip.s_addr;
                    135:        uw->gw = gateip.s_addr;
                    136:        uw->src_port = d->myport;
                    137:        uw->dst_port = d->destport;
                    138:        uw->buffer_size = len;
                    139:        uw->buffer.segment = VTOPSEG(pkt);
                    140:        uw->buffer.offset = VTOPOFF(pkt);
                    141:
                    142:        pxe_call(PXENV_UDP_WRITE);
                    143:
                    144:        if (uw->status != PXENV_STATUS_SUCCESS) {
                    145:                /* XXX This happens a lot; it shouldn't. */
                    146:                if (uw->status != PXENV_STATUS_FAILURE)
                    147:                        printf("sendudp: PXENV_UDP_WRITE failed: 0x%x\n",
                    148:                            uw->status);
                    149:                return -1;
                    150:        }
                    151:
                    152:        return len;
                    153: }
                    154:
                    155: /*
                    156:  * Receive a UDP packet and validate it for us.
                    157:  * Caller leaves room for the headers (Ether, IP, UDP).
                    158:  */
                    159: ssize_t
                    160: pxereadudp(struct iodesc *d, void *pkt, size_t len, time_t tleft)
                    161: {
                    162:        t_PXENV_UDP_READ *ur = (void *) pxe_command_buf;
                    163:        struct udphdr *uh;
                    164:        struct ip *ip;
                    165:
                    166:        uh = (struct udphdr *)pkt - 1;
                    167:        ip = (struct ip *)uh - 1;
                    168:
                    169:        bzero(ur, sizeof(*ur));
                    170:
                    171:        ur->dest_ip = d->myip.s_addr;
                    172:        ur->d_port = d->myport;
                    173:        ur->buffer_size = len;
                    174:        ur->buffer.segment = VTOPSEG(pkt);
                    175:        ur->buffer.offset = VTOPOFF(pkt);
                    176:
                    177:        /* XXX Timeout unused. */
                    178:
                    179:        pxe_call(PXENV_UDP_READ);
                    180:
                    181:        if (ur->status != PXENV_STATUS_SUCCESS) {
                    182:                /* XXX This happens a lot; it shouldn't. */
                    183:                if (ur->status != PXENV_STATUS_FAILURE)
                    184:                        printf("readudp: PXENV_UDP_READ_failed: 0x%0x\n",
                    185:                            ur->status);
                    186:                return -1;
                    187:        }
                    188:
                    189:        ip->ip_src.s_addr = ur->src_ip;
                    190:        uh->uh_sport = ur->s_port;
                    191:        uh->uh_dport = d->myport;
                    192:
                    193:        return ur->buffer_size;
                    194: }
                    195:
                    196: /*
                    197:  * netif layer:
                    198:  *  open, close, shutdown: called from dev_net.c
                    199:  *  socktodesc: called by network protocol modules
                    200:  *
                    201:  * We only allow one open socket.
                    202:  */
                    203:
                    204: static int pxe_inited;
                    205: static struct iodesc desc;
                    206:
                    207: int
                    208: pxe_netif_open()
                    209: {
                    210:        t_PXENV_UDP_OPEN *uo = (void *) pxe_command_buf;
                    211:
                    212: #ifdef NETIF_DEBUG
                    213:        printf("pxe_netif_open()\n");
                    214: #endif
                    215:        if (!pxe_inited) {
                    216:                if (pxe_init(0) != 0)
                    217:                        return -1;
                    218:                pxe_inited = 1;
                    219:        }
                    220:        /* BI_ADD(&bi_netif, BTINFO_NETIF, sizeof(bi_netif)); */
                    221:
                    222:        bzero(uo, sizeof(*uo));
                    223:
                    224:        uo->src_ip = bootplayer.yip;
                    225:
                    226:        pxe_call(PXENV_UDP_OPEN);
                    227:
                    228:        if (uo->status != PXENV_STATUS_SUCCESS) {
                    229:                printf("\npxe_netif_open: PXENV_UDP_OPEN failed: 0x%x\n",
                    230:                    uo->status);
                    231:                return -1;
                    232:        }
                    233:
                    234:        bcopy(bootplayer.CAddr, desc.myea, ETHER_ADDR_LEN);
                    235:        bootmac = bootplayer.CAddr;
                    236:
                    237:        /*
                    238:         * Since the PXE BIOS has already done DHCP, make sure we
                    239:         * don't reuse any of its transaction IDs.
                    240:         */
                    241:        desc.xid = bootplayer.ident;
                    242:
                    243:        return 0;
                    244: }
                    245:
                    246: void
                    247: pxe_netif_close(sock)
                    248:        int sock;
                    249: {
                    250:        t_PXENV_UDP_CLOSE *uc = (void *) pxe_command_buf;
                    251:
                    252: #ifdef NETIF_DEBUG
                    253:        if (sock != 0)
                    254:                printf("pxe_netif_close: sock=%d\n", sock);
                    255: #endif
                    256:
                    257:        uc->status = 0;
                    258:
                    259:        pxe_call(PXENV_UDP_CLOSE);
                    260:
                    261:        if (uc->status != PXENV_STATUS_SUCCESS)
                    262:                printf("pxe_netif_end: PXENV_UDP_CLOSE failed: 0x%x\n",
                    263:                    uc->status);
                    264: }
                    265:
                    266: void
                    267: pxe_netif_shutdown()
                    268: {
                    269: #ifdef NETIF_DEBUG
                    270:        printf("pxe_netif_shutdown()\n");
                    271: #endif
                    272:
                    273:        pxe_shutdown();
                    274: }
                    275:
                    276: struct iodesc *
                    277: pxesocktodesc(sock)
                    278:        int sock;
                    279: {
                    280:
                    281: #ifdef NETIF_DEBUG
                    282:        if (sock != 0)
                    283:                return 0;
                    284:        else
                    285: #endif
                    286:                return &desc;
                    287: }
                    288:
                    289: /*****************************************************************************
                    290:  * PXE initialization and support routines
                    291:  *****************************************************************************/
                    292:
                    293: u_int16_t pxe_command_buf_seg;
                    294: u_int16_t pxe_command_buf_off;
                    295:
                    296: extern u_int16_t bangpxe_off, bangpxe_seg;
                    297: extern u_int16_t pxenv_off, pxenv_seg;
                    298:
                    299: /* static struct btinfo_netif bi_netif; */
                    300:
                    301: void
                    302: pxeprobe(void)
                    303: {
                    304:        if (!pxe_inited) {
                    305:                if (pxe_init(1) == 0) {
                    306:                        pxe_inited = 1;
                    307:                }
                    308:        }
                    309: }
                    310:
                    311: int
                    312: pxe_init(int quiet)
                    313: {
                    314:        t_PXENV_GET_CACHED_INFO *gci = (void *) pxe_command_buf;
                    315:        pxenv_t *pxenv;
                    316:        pxe_t *pxe;
                    317:        char *cp;
                    318:        int i;
                    319:        u_int8_t cksum, *ucp;
                    320:
                    321:        /*
                    322:         * Checking for the presence of PXE is a machine-dependent
                    323:         * operation.  On the IA-32, this can be done two ways:
                    324:         *
                    325:         *      Int 0x1a function 0x5650
                    326:         *
                    327:         *      Scan memory for the !PXE or PXENV+ signatures
                    328:         *
                    329:         * We do the latter, since the Int method returns a pointer
                    330:         * to a deprecated structure (PXENV+).
                    331:         */
                    332:
                    333:        pxenv = NULL;
                    334:        pxe = NULL;
                    335:
                    336:        for (cp = (char *)0xa0000; cp > (char *)0x10000; cp -= 2) {
                    337:                if (pxenv == NULL) {
                    338:                        pxenv = (pxenv_t *)cp;
                    339:                        if (memcmp(pxenv->Signature, S_SIZE("PXENV+")) != 0)
                    340:                                pxenv = NULL;
                    341:                        else {
                    342:                                for (i = 0, ucp = (u_int8_t *)cp, cksum = 0;
                    343:                                     i < pxenv->Length; i++)
                    344:                                        cksum += ucp[i];
                    345:                                if (cksum != 0) {
                    346:                                        printf("\npxe_init: bad cksum (0x%x) "
                    347:                                            "for PXENV+ at 0x%lx\n", cksum,
                    348:                                            (u_long) cp);
                    349:                                        pxenv = NULL;
                    350:                                }
                    351:                        }
                    352:                }
                    353:
                    354:                if (pxe == NULL) {
                    355:                        pxe = (pxe_t *)cp;
                    356:                        if (memcmp(pxe->Signature, S_SIZE("!PXE")) != 0)
                    357:                                pxe = NULL;
                    358:                        else {
                    359:                                for (i = 0, ucp = (u_int8_t *)cp, cksum = 0;
                    360:                                     i < pxe->StructLength; i++)
                    361:                                        cksum += ucp[i];
                    362:                                if (cksum != 0) {
                    363:                                        printf("pxe_init: bad cksum (0x%x) "
                    364:                                            "for !PXE at 0x%lx\n", cksum,
                    365:                                            (u_long) cp);
                    366:                                        pxe = NULL;
                    367:                                }
                    368:                        }
                    369:                }
                    370:
                    371:                if (pxe != NULL && pxenv != NULL)
                    372:                        break;
                    373:        }
                    374:
                    375:        if (pxe == NULL && pxenv == NULL) {
                    376:                if (!quiet) printf("pxe_init: No PXE BIOS found.\n");
                    377:                return 1;
                    378:        }
                    379:
                    380:        if (pxenv == NULL) {
                    381:                /* assert(pxe != NULL); */
                    382:
                    383:                printf(quiet ? " pxe!" : "PXE present\n");
                    384:        } else {                                /* pxenv != NULL */
                    385:                int bang = 0;
                    386:
                    387:                if (pxenv->Version >= 0x0201 && pxe != NULL) {
                    388:                        /* 2.1 or greater -- don't use PXENV+ */
                    389:                        bang = 1;
                    390:                }
                    391:
                    392:                if (quiet) {
                    393:                        printf(" pxe%c[%d.%d]",
                    394:                            (bang ? '!' : '+'),
                    395:                            (pxenv->Version >> 8) & 0xff,
                    396:                             pxenv->Version & 0xff);
                    397:                } else {
                    398:                        printf("PXE BIOS Version %d.%d\n",
                    399:                            (pxenv->Version >> 8) & 0xff,
                    400:                             pxenv->Version & 0xff);
                    401:                }
                    402:
                    403:                if (bang) {
                    404:                        pxenv = NULL;
                    405:                }
                    406:        }
                    407:
                    408:        if (pxenv == NULL) {
                    409:                pxe_call = pxecall_bangpxe;
                    410:                bangpxe_off = pxe->EntryPointSP.offset;
                    411:                bangpxe_seg = pxe->EntryPointSP.segment;
                    412:        } else {
                    413:                pxe_call = pxecall_pxenv;
                    414:                pxenv_off = pxenv->RMEntry.offset;
                    415:                pxenv_seg = pxenv->RMEntry.segment;
                    416:        }
                    417:
                    418:        /*
                    419:         * Pre-compute the segment/offset of the pxe_command_buf
                    420:         * to make things nicer in the low-level calling glue.
                    421:         */
                    422:        pxe_command_buf_seg = VTOPSEG(pxe_command_buf);
                    423:        pxe_command_buf_off = VTOPOFF(pxe_command_buf);
                    424:
                    425:        /*
                    426:         * Get the cached info from the server's Discovery reply packet.
                    427:         */
                    428:        bzero(gci, sizeof(*gci));
                    429:        gci->PacketType = PXENV_PACKET_TYPE_CACHED_REPLY;
                    430:        pxe_call(PXENV_GET_CACHED_INFO);
                    431:
                    432:        if (gci->Status != PXENV_STATUS_SUCCESS) {
                    433:                printf("\npxeinfo: PXENV_GET_CACHED_INFO failed: 0x%x\n",
                    434:                    gci->Status);
                    435:                return 1;
                    436:        }
                    437:
                    438:        memcpy(&bootplayer,
                    439:            SEGOFF2FLAT(gci->Buffer.segment, gci->Buffer.offset),
                    440:            gci->BufferSize);
                    441:
                    442:        bcopy(&bootplayer.yip, &myip.s_addr, sizeof(myip.s_addr));
                    443:        bcopy(&bootplayer.sip, &servip.s_addr, sizeof(servip.s_addr));
                    444:
                    445:         /* Compute our "natural" netmask. */
                    446:        if (IN_CLASSA(myip.s_addr))
                    447:                netmask = IN_CLASSA_NET;
                    448:        else if (IN_CLASSB(myip.s_addr))
                    449:                netmask = IN_CLASSB_NET;
                    450:        else
                    451:                netmask = IN_CLASSC_NET;
                    452:
                    453:        return 0;
                    454: }
                    455:
                    456: void
                    457: pxeinfo(void)
                    458: {
                    459:        u_int8_t *p;
                    460: #ifdef PXE_DEBUG
                    461:        t_PXENV_UNDI_GET_NIC_TYPE *gnt = (void *) pxe_command_buf;
                    462: #endif
                    463:
                    464:        printf(" mac %s", ether_sprintf(bootplayer.CAddr));
                    465:        p = (u_int8_t *)&myip.s_addr;
                    466:        printf(", ip %d.%d.%d.%d", p[0], p[1], p[2], p[3]);
                    467:        p = (u_int8_t *)&servip.s_addr;
                    468:        printf(", server %d.%d.%d.%d", p[0], p[1], p[2], p[3]);
                    469:
                    470: #ifdef PXE_DEBUG
                    471:        /*
                    472:         * Get network interface information.
                    473:         */
                    474:        bzero(gnt, sizeof(*gnt));
                    475:        pxe_call(PXENV_UNDI_GET_NIC_TYPE);
                    476:
                    477:        if (gnt->Status != PXENV_STATUS_SUCCESS) {
                    478:                printf("\npxeinfo: PXENV_UNDI_GET_NIC_TYPE failed: 0x%x\n",
                    479:                    gnt->Status);
                    480:                return;
                    481:        }
                    482:
                    483:        switch (gnt->NicType) {
                    484:        case PCI_NIC:
                    485:        case CardBus_NIC:
                    486:                /* strncpy(bi_netif.ifname, "pxe", sizeof(bi_netif.ifname)); */
                    487:                /* bi_netif.bus = BI_BUS_PCI; */
                    488:                /* bi_netif.addr.tag = gnt->info.pci.BusDevFunc; */
                    489:
                    490:                printf("\nPXE: Using %s device at bus %d device %d function %d\n",
                    491:                    gnt->NicType == PCI_NIC ? "PCI" : "CardBus",
                    492:                    (gnt->info.pci.BusDevFunc >> 8) & 0xff,
                    493:                    (gnt->info.pci.BusDevFunc >> 3) & 0x1f,
                    494:                    gnt->info.pci.BusDevFunc & 0x7);
                    495:                break;
                    496:
                    497:        case PnP_NIC:
                    498:                /* XXX Make bootinfo work with this. */
                    499:                printf("\nPXE: Using PnP device at 0x%x\n",
                    500:                    gnt->info.pnp.CardSelNum);
                    501:        }
                    502: #endif
                    503: }
                    504:
                    505: void
                    506: pxe_shutdown(void)
                    507: {
                    508:        int try;
                    509:        t_PXENV_UNLOAD_STACK *unload = (void *) pxe_command_buf;
                    510:        t_PXENV_UNDI_SHUTDOWN *shutdown = (void *) pxe_command_buf;
                    511: #ifdef PXE_DEBUG
                    512:        t_PXENV_UDP_CLOSE *close = (void *) pxe_command_buf;
                    513: #endif
                    514:
                    515:        if (pxe_call == NULL)
                    516:                return;
                    517:
                    518:        /* Close any open UDP connections.  Ignore return value. */
                    519:        pxe_call(PXENV_UDP_CLOSE);
                    520: #ifdef PXE_DEBUG
                    521:        printf("pxe_shutdown: PXENV_UDP_CLOSE returned 0x%x\n", close->status);
                    522: #endif
                    523:
                    524:        /* Sometimes PXENV_UNDI_SHUTDOWN doesn't work at first */
                    525:        for (try = 3; try > 0; try--) {
                    526:                pxe_call(PXENV_UNDI_SHUTDOWN);
                    527:
                    528:                if (shutdown->Status == PXENV_STATUS_SUCCESS)
                    529:                        break;
                    530:
                    531:                printf("pxe_shutdown: PXENV_UNDI_SHUTDOWN failed: 0x%x\n",
                    532:                    shutdown->Status);
                    533:
                    534:                if (try != 1)
                    535:                        sleep(1);
                    536:        }
                    537:
                    538:        /* Have multiple attempts at PXENV_UNLOAD_STACK, too */
                    539:        for (try = 3; try > 0; try--) {
                    540:                pxe_call(PXENV_UNLOAD_STACK);
                    541:
                    542:                if (unload->Status == PXENV_STATUS_SUCCESS)
                    543:                        break;
                    544:
                    545:                printf("pxe_shutdown: PXENV_UNLOAD_STACK failed: 0x%x\n",
                    546:                    unload->Status);
                    547:
                    548:                if (try != 1)
                    549:                        sleep(1);
                    550:        }
                    551: }

CVSweb