diff options
-rw-r--r-- | src/core/async.c | 474 | ||||
-rw-r--r-- | src/core/btext.c | 4 | ||||
-rw-r--r-- | src/core/debug.c | 1 | ||||
-rw-r--r-- | src/core/dev.c | 7 | ||||
-rw-r--r-- | src/core/disk.c | 4 | ||||
-rw-r--r-- | src/core/misc.c | 5 | ||||
-rw-r--r-- | src/core/pcmcia.c | 15 | ||||
-rw-r--r-- | src/core/random.c | 1 | ||||
-rw-r--r-- | src/core/serial.c | 2 | ||||
-rw-r--r-- | src/include/gpxe/icmp6.h | 4 | ||||
-rw-r--r-- | src/include/gpxe/ndp.h | 1 | ||||
-rw-r--r-- | src/net/icmpv6.c | 2 | ||||
-rw-r--r-- | src/proto/igmp.c | 20 | ||||
-rw-r--r-- | src/proto/nfs.c | 10 | ||||
-rw-r--r-- | src/proto/tftm.c | 208 | ||||
-rw-r--r-- | src/proto/tftpcore.c | 541 |
16 files changed, 44 insertions, 1255 deletions
diff --git a/src/core/async.c b/src/core/async.c deleted file mode 100644 index d1ae077..0000000 --- a/src/core/async.c +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include <string.h> -#include <errno.h> -#include <assert.h> -#include <gpxe/process.h> -#include <gpxe/async.h> - -/** @file - * - * Asynchronous operations - * - */ - -/** - * Name signal - * - * @v signal Signal number - * @ret name Name of signal - */ -static inline __attribute__ (( always_inline )) const char * -signal_name ( enum signal signal ) { - switch ( signal ) { - case SIGCHLD: return "SIGCHLD"; - case SIGKILL: return "SIGKILL"; - case SIGUPDATE: return "SIGUPDATE"; - default: return "SIG<UNKNOWN>"; - } -} - -/** - * Initialise an asynchronous operation - * - * @v async Asynchronous operation - * @v aop Asynchronous operation operations to use - * @v parent Parent asynchronous operation, or NULL - * @ret aid Asynchronous operation ID - * - * It is valid to create an asynchronous operation with no parent - * operation; see async_init_orphan(). - */ -aid_t async_init ( struct async *async, struct async_operations *aop, - struct async *parent ) { - static aid_t aid = 1; - - /* Assign identifier. Negative IDs are used to indicate - * errors, so avoid assigning them. - */ - ++aid; - aid &= ( ( ~( ( aid_t ) 0 ) ) >> 1 ); - - DBGC ( async, "ASYNC %p (type %p) initialising as", async, aop ); - if ( parent ) { - DBGC ( async, " child of ASYNC %p", parent ); - } else { - DBGC ( async, " orphan" ); - } - DBGC ( async, " with ID %ld\n", aid ); - - assert ( async != NULL ); - assert ( aop != NULL ); - - /* Add to hierarchy */ - if ( parent ) { - async->parent = parent; - list_add ( &async->siblings, &parent->children ); - } - INIT_LIST_HEAD ( &async->children ); - - /* Initialise fields */ - async->rc = -EINPROGRESS; - async->completed = 0; - async->total = 0; - async->aop = aop; - async->aid = aid; - - return async->aid; -} - -/** - * Uninitialise an asynchronous operation - * - * @v async Asynchronous operation - * - * Abandon an asynchronous operation without signalling the parent. - * You may do this only during the period between calling async_init() - * and returning to the parent for the first time. It is designed to - * simplify the error paths of asynchronous operations that themselves - * spawn further asynchronous operations. - * - * An example may help: - * - * int start_something ( ..., struct async *parent ) { - * struct my_data_structure *myself; - * - * ... allocate memory for myself ... - * - * async_init ( &myself->async, &my_async_operations, parent ); - * if ( ( rc = start_child_operation ( ..., &myself->async ) ) != 0 ) { - * async_uninit ( &myself->async ); - * return rc; - * } - * - * return 0; - * } - * - * It is valid to call async_uninit() on an asynchronous operation - * that has not yet been initialised (i.e. a zeroed-out @c struct @c - * async). - */ -void async_uninit ( struct async *async ) { - - assert ( async != NULL ); - - if ( async->parent ) { - assert ( list_empty ( &async->children ) ); - - DBGC ( async, "ASYNC %p uninitialising\n", async ); - list_del ( &async->siblings ); - } -} - -/** - * SIGCHLD 'ignore' handler - * - * @v async Asynchronous operation - * @v signal Signal received - */ -static void async_ignore_sigchld ( struct async *async, enum signal signal ) { - aid_t waited_aid; - - assert ( async != NULL ); - assert ( signal == SIGCHLD ); - - /* Reap the child */ - waited_aid = async_wait ( async, NULL, 0 ); - assert ( waited_aid >= 0 ); -} - -/** - * SIGUPDATE 'ignore' handler - * - * @v async Asynchronous operation - * @v signal Signal received - */ -static void async_ignore_sigupdate ( struct async *async, - enum signal signal ) { - struct async *child; - - assert ( async != NULL ); - assert ( signal == SIGUPDATE ); - - async_signal_children ( async, signal ); - async->completed = 0; - async->total = 0; - list_for_each_entry ( child, &async->children, siblings ) { - async->completed += child->completed; - async->total += child->total; - } -} - -/** - * 'Ignore' signal handler - * - * @v async Asynchronous operation - * @v signal Signal received - */ -void async_ignore_signal ( struct async *async, enum signal signal ) { - - DBGC ( async, "ASYNC %p using ignore handler for %s\n", - async, signal_name ( signal ) ); - - assert ( async != NULL ); - - switch ( signal ) { - case SIGCHLD: - async_ignore_sigchld ( async, signal ); - break; - case SIGUPDATE: - async_ignore_sigupdate ( async, signal ); - break; - case SIGKILL: - default: - /* Nothing to do */ - break; - } -} - -/** - * Default signal handler - * - * @v async Asynchronous operation - * @v signal Signal received - */ -static void async_default_signal ( struct async *async, enum signal signal ) { - - DBGC ( async, "ASYNC %p using default handler for %s\n", - async, signal_name ( signal ) ); - - assert ( async != NULL ); - - switch ( signal ) { - case SIGCHLD: - case SIGKILL: - case SIGUPDATE: - default: - /* Nothing to do */ - break; - } -} - -/** - * Send signal to asynchronous operation - * - * @v async Asynchronous operation - * @v signal Signal to send - */ -void async_signal ( struct async *async, enum signal signal ) { - signal_handler_t handler; - - DBGC ( async, "ASYNC %p receiving %s\n", - async, signal_name ( signal ) ); - - assert ( async != NULL ); - assert ( async->aop != NULL ); - assert ( signal < SIGMAX ); - - handler = async->aop->signal[signal]; - if ( handler ) { - /* Use the asynchronous operation's signal handler */ - handler ( async, signal ); - } else { - /* Use the default handler */ - async_default_signal ( async, signal ); - } -} - -/** - * Send signal to all child asynchronous operations - * - * @v async Asynchronous operation - * @v signal Signal to send - */ -void async_signal_children ( struct async *async, enum signal signal ) { - struct async *child; - struct async *tmp; - - assert ( async != NULL ); - - list_for_each_entry_safe ( child, tmp, &async->children, siblings ) { - async_signal ( child, signal ); - } -} - -/** - * Reap default handler - * - * @v async Asynchronous operation - */ -static void async_reap_default ( struct async *async ) { - - DBGC ( async, "ASYNC %p ignoring REAP\n", async ); - - assert ( async != NULL ); - - /* Nothing to do */ -} - -/** - * Reap asynchronous operation - * - * @v async Asynchronous operation - * - * Note that the asynchronous operation should have been freed by - * calling this function; you may not dereference @c async after this - * call. - */ -static void async_reap ( struct async *async ) { - - DBGC ( async, "ASYNC %p being reaped, exit status %d (%s)\n", - async, async->rc, strerror ( async->rc ) ); - - assert ( async != NULL ); - assert ( async->aop != NULL ); - assert ( list_empty ( &async->children ) ); - - /* Unlink from hierarchy */ - if ( async->parent ) - list_del ( &async->siblings ); - async->parent = NULL; - - /* Release all resources */ - if ( async->aop->reap ) { - async->aop->reap ( async ); - } else { - async_reap_default ( async ); - } -} - -/** - * Mark asynchronous operation as complete - * - * @v async Asynchronous operation - * @v rc Return status code - * - * An asynchronous operation should call this once it has completed. - * After calling async_done(), it must be prepared to be reaped by - * having its reap() method called. - */ -void async_done ( struct async *async, int rc ) { - struct async *child; - struct async *tmp; - - DBGC ( async, "ASYNC %p completing with status %d (%s)\n", - async, rc, strerror ( rc ) ); - - assert ( async != NULL ); - assert ( async->parent != NULL ); - assert ( rc != -EINPROGRESS ); - - /* Store return status code */ - async->rc = rc; - - /* Disown all of our children */ - list_for_each_entry_safe ( child, tmp, &async->children, siblings ) { - DBGC ( async, "ASYNC %p disowning child ASYNC %p\n", - async, child ); - list_del ( &child->siblings ); - child->parent = NULL; - } - - /* Send SIGCHLD to parent. If we don't have a parent then we - * have to take care of our own funeral arrangements. - */ - if ( async->parent ) { - async_signal ( async->parent, SIGCHLD ); - } else { - async_reap ( async ); - } -} - -/** - * Wait for any child asynchronous operation to complete - * - * @v child Child asynchronous operation - * @v rc Child exit status to fill in, or NULL - * @v block Block waiting for child operation to complete - * @ret aid Asynchronous operation ID, or -1 on error - */ -aid_t async_wait ( struct async *async, int *rc, int block ) { - struct async *child; - aid_t child_aid; - int dummy_rc; - - DBGC ( async, "ASYNC %p performing %sblocking wait%s\n", async, - ( block ? "" : "non-" ), ( rc ? "" : " (ignoring status)" ) ); - - assert ( async != NULL ); - - /* Avoid multiple tests for "if ( rc )" */ - if ( ! rc ) - rc = &dummy_rc; - - while ( 1 ) { - - /* Return immediately if we have no children */ - if ( list_empty ( &async->children ) ) { - DBGC ( async, "ASYNC %p has no more children\n", - async ); - *rc = -ECHILD; - return -1; - } - - /* Look for a completed child */ - list_for_each_entry ( child, &async->children, siblings ) { - if ( child->rc == -EINPROGRESS ) - continue; - - /* Found a completed child */ - *rc = child->rc; - child_aid = child->aid; - - DBGC ( async, "ASYNC %p reaping child ASYNC %p " - "(ID %ld)\n", async, child, child_aid ); - - /* Reap the child and return */ - async_reap ( child ); - return child_aid; - } - - /* Return immediately if non-blocking */ - if ( ! block ) { - *rc = -EINPROGRESS; - return -1; - } - - /* Allow processes to run */ - step(); - } -} - -/** - * Wait for any child asynchronous operation to complete, with progress bar - * - * @v child Child asynchronous operation - * @v rc Child exit status to fill in, or NULL - * @ret aid Asynchronous operation ID, or -1 on error - */ -aid_t async_wait_progress ( struct async *async, int *rc ) { - struct async *child; - long last_progress = -1; - long progress; - aid_t child_aid; - - do { - step(); - async_signal ( async, SIGUPDATE ); - if ( async->total ) { - progress = ( async->completed / (async->total / 100) ); - if ( progress != last_progress ) - printf ( "\rProgress: %d%%", progress ); - last_progress = progress; - } - child_aid = async_wait ( async, rc, 0 ); - } while ( *rc == -EINPROGRESS ); - - printf ( "\n" ); - return child_aid; -} - -/** - * Default asynchronous operations - * - * The default is to ignore SIGCHLD (i.e. to automatically reap - * children) and to use the default handler (i.e. do nothing) for all - * other signals. - */ -struct async_operations default_async_operations = { - .signal = { - [SIGCHLD] = SIG_IGN, - [SIGUPDATE] = SIG_IGN, - }, -}; - -/** - * Default asynchronous operations for orphan asynchronous operations - * - * The default for orphan asynchronous operations is to do nothing for - * SIGCHLD (i.e. to not automatically reap children), on the - * assumption that you're probably creating the orphan solely in order - * to async_wait() on it. - */ -struct async_operations orphan_async_operations = { - .signal = { - [SIGCHLD] = SIG_DFL, - [SIGUPDATE] = SIG_IGN, - }, -}; diff --git a/src/core/btext.c b/src/core/btext.c index 409c429..6e1a29e 100644 --- a/src/core/btext.c +++ b/src/core/btext.c @@ -1,3 +1,5 @@ +#if 0 + /* * Procedures for drawing on the screen early on in the boot process. * @@ -5037,3 +5039,5 @@ static const unsigned char vga_font[cmapsz] BTDATA = { 0x00, /* 00000000 */ #endif }; + +#endif diff --git a/src/core/debug.c b/src/core/debug.c index 4754bfd..d72b3df 100644 --- a/src/core/debug.c +++ b/src/core/debug.c @@ -1,3 +1,4 @@ +#include <stdio.h> #include <stdint.h> #include <stdarg.h> #include <io.h> diff --git a/src/core/dev.c b/src/core/dev.c index 541a9eb..582edf8 100644 --- a/src/core/dev.c +++ b/src/core/dev.c @@ -1,6 +1,7 @@ -#include "etherboot.h" -#include "stddef.h" -#include "dev.h" +#include <stdio.h> +#include <etherboot.h> +#include <stddef.h> +#include <dev.h> /* * Each bus driver defines several methods, which are described in diff --git a/src/core/disk.c b/src/core/disk.c index 2ccd5ff..415fdec 100644 --- a/src/core/disk.c +++ b/src/core/disk.c @@ -1,5 +1,5 @@ -#include "etherboot.h" -#include "disk.h" +#include <etherboot.h> +#include <disk.h> #warning "disk.c is currently broken" #if 0 diff --git a/src/core/misc.c b/src/core/misc.c index e214a62..5839913 100644 --- a/src/core/misc.c +++ b/src/core/misc.c @@ -2,9 +2,10 @@ MISC Support Routines **************************************************************************/ -#include "etherboot.h" -#include "console.h" +#include <etherboot.h> +#include <console.h> #include <stdlib.h> +#include <stdio.h> /************************************************************************** IPCHKSUM - Checksum IP Header diff --git a/src/core/pcmcia.c b/src/core/pcmcia.c index c15fe9c..53d4541 100644 --- a/src/core/pcmcia.c +++ b/src/core/pcmcia.c @@ -1,3 +1,5 @@ +#if 0 + /* * pcmcia.c * @@ -23,12 +25,13 @@ * at some point. If there's anything obvious or better, not-so-obvious, * please contact me by e-mail: anselm (AT) hoffmeister (DOT) be *THANKS* */ -#include "pcmcia.h" -#include "i82365.h" +#include <stdio.h> +#include <pcmcia.h> +#include <i82365.h> #define CODE_STATUS "alpha" #define CODE_VERSION "0.1.3" -#include "pcmcia-opts.h" -#include "console.h" +#include <pcmcia-opts.h> +#include <console.h> #include <gpxe/init.h> int sockets; /* AHTODO: Phase this out! */ @@ -118,7 +121,7 @@ static void pcmcia_init_all(void) { printf ( "]\nHighest config available is %d\n", uc[2*(ui+3)] ); m = uc[2*(ui+2)]; pccsock[i].configoffset = 0; - for ( j = 0; j <= m & 3; ++j ) { + for ( j = 0; j <= (m & 3); ++j ) { pccsock[i].configoffset += uc[2*(ui+4+j)] << (8*j); } pccsock[i].rmask0 = 0; @@ -262,3 +265,5 @@ static void pcmcia_shutdown_all(void) { } INIT_FN ( INIT_PCMCIA, pcmcia_init_all, NULL, pcmcia_shutdown_all ); + +#endif diff --git a/src/core/random.c b/src/core/random.c index c15bb6d..0d914c9 100644 --- a/src/core/random.c +++ b/src/core/random.c @@ -5,6 +5,7 @@ */ #include <stdlib.h> +#include <etherboot.h> static int32_t rnd_seed = 0; diff --git a/src/core/serial.c b/src/core/serial.c index d082748..6304037 100644 --- a/src/core/serial.c +++ b/src/core/serial.c @@ -213,7 +213,7 @@ static void serial_init ( void ) { /* rx buffer reg * throw away (unconditionally the first time) */ - uart_readb(UART_BASE + UART_RBR); + (void) uart_readb(UART_BASE + UART_RBR); /* line status reg */ status = uart_readb(UART_BASE + UART_LSR); } while(status & UART_LSR_DR); diff --git a/src/include/gpxe/icmp6.h b/src/include/gpxe/icmp6.h index 3521036..76aaddf 100644 --- a/src/include/gpxe/icmp6.h +++ b/src/include/gpxe/icmp6.h @@ -32,7 +32,7 @@ struct neighbour_solicit { /* "Compulsory" options */ uint8_t opt_type; uint8_t opt_len; -#warning hack alert + /* FIXME: hack alert */ uint8_t opt_ll_addr[6]; }; @@ -45,7 +45,7 @@ struct neighbour_advert { struct in6_addr target; uint8_t opt_type; uint8_t opt_len; -#warning hack alert + /* FIXME: hack alert */ uint8_t opt_ll_addr[6]; }; diff --git a/src/include/gpxe/ndp.h b/src/include/gpxe/ndp.h index e3711b5..2eae295 100644 --- a/src/include/gpxe/ndp.h +++ b/src/include/gpxe/ndp.h @@ -16,7 +16,6 @@ #define NDP_STATE_PROBE 4 #define NDP_STATE_STALE 5 -static struct ndp_entry * ndp_find_entry ( struct in6_addr *in6 ); int ndp_resolve ( struct net_device *netdev, struct in6_addr *src, struct in6_addr *dest, void *dest_ll_addr ); int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, diff --git a/src/net/icmpv6.c b/src/net/icmpv6.c index da73892..69824c5 100644 --- a/src/net/icmpv6.c +++ b/src/net/icmpv6.c @@ -72,7 +72,7 @@ int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src __unuse * @v st_dest Destination address */ static int icmp6_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, - struct sockaddr_tcpip *st_dest ) { + struct sockaddr_tcpip *st_dest, __unused uint16_t pshdr_csum ) { struct icmp6_header *icmp6hdr = iobuf->data; /* Sanity check */ diff --git a/src/proto/igmp.c b/src/proto/igmp.c index 4d4df90..d61f6c4 100644 --- a/src/proto/igmp.c +++ b/src/proto/igmp.c @@ -3,11 +3,11 @@ * */ -#include "ip.h" -#include "igmp.h" -#include "background.h" -#include "nic.h" -#include "etherboot.h" +#include <ip.h> +#include <igmp.h> +#include <background.h> +#include <nic.h> +#include <etherboot.h> static unsigned long last_igmpv1 = 0; static struct igmptable_t igmptable[MAX_IGMP]; @@ -50,8 +50,7 @@ static void send_igmp_reports ( unsigned long now ) { igmp.igmp.chksum = ipchksum ( &igmp.igmp, sizeof ( igmp.igmp ) ); ip_transmit ( sizeof ( igmp ), &igmp ); - DBG ( "IGMP sent report to %@\n", - igmp.igmp.group.s_addr ); + DBG ( "IGMP sent report to %s\n", inet_ntoa ( igmp.igmp.group ) ); /* Don't send another igmp report until asked */ igmptable[i].time = 0; } @@ -84,7 +83,7 @@ static void process_igmp ( unsigned long now, unsigned short ptype __unused, interval = ( igmp->response_time * TICKS_PER_SEC ) /10; } - DBG ( "IGMP received query for %@\n", igmp->group.s_addr ); + DBG ( "IGMP received query for %s\n", inet_ntoa ( igmp->group ) ); for ( i = 0 ; i < MAX_IGMP ; i++ ) { uint32_t group = igmptable[i].group.s_addr; if ( ( group == 0 ) || @@ -101,7 +100,8 @@ static void process_igmp ( unsigned long now, unsigned short ptype __unused, if ( ( ( igmp->type == IGMPv1_REPORT ) || ( igmp->type == IGMPv2_REPORT ) ) && ( ip->dest.s_addr == igmp->group.s_addr ) ) { - DBG ( "IGMP received report for %@\n", igmp->group.s_addr); + DBG ( "IGMP received report for %s\n", + inet_ntoa ( igmp->group ) ); for ( i = 0 ; i < MAX_IGMP ; i++ ) { if ( ( igmptable[i].group.s_addr == igmp->group.s_addr ) && @@ -142,7 +142,7 @@ void leave_group ( int slot ) { igmp.igmp.group.s_addr = igmptable[slot].group.s_addr; igmp.igmp.chksum = ipchksum ( &igmp.igmp, sizeof ( igmp ) ); ip_transmit ( sizeof ( igmp ), &igmp ); - DBG ( "IGMP left group %@\n", igmp.igmp.group.s_addr ); + DBG ( "IGMP left group %s\n", inet_ntoa ( igmp.igmp.group ) ); } memset ( &igmptable[slot], 0, sizeof ( igmptable[0] ) ); } diff --git a/src/proto/nfs.c b/src/proto/nfs.c index c700ed6..2743287 100644 --- a/src/proto/nfs.c +++ b/src/proto/nfs.c @@ -49,7 +49,7 @@ static void rpc_printerror(struct rpc_t *rpc) rpc->u.reply.astatus) { /* rpc_printerror() is called for any RPC related error, * suppress output if no low level RPC error happened. */ - DBG("RPC error: (%d,%d,%d)\n", ntohl(rpc->u.reply.rstatus), + DBG("RPC error: (%ld,%ld,%ld)\n", ntohl(rpc->u.reply.rstatus), ntohl(rpc->u.reply.verifier), ntohl(rpc->u.reply.astatus)); } @@ -503,14 +503,14 @@ static int nfs ( char *url __unused, struct sockaddr_in *server, mount_server.sin_addr = nfs_server.sin_addr = server->sin_addr; mount_server.sin_port = rpc_lookup(server, PROG_MOUNT, 1, sport); if ( ! mount_server.sin_port ) { - DBG ( "Cannot get mount port from %!:%d\n", - server->sin_addr.s_addr, server->sin_port ); + DBG ( "Cannot get mount port from %s:%d\n", + inet_ntoa ( server->sin_addr ), server->sin_port ); return 0; } nfs_server.sin_port = rpc_lookup(server, PROG_NFS, 2, sport); if ( ! mount_server.sin_port ) { - DBG ( "Cannot get nfs port from %!:%d\n", - server->sin_addr.s_addr, server->sin_port ); + DBG ( "Cannot get nfs port from %s:%d\n", + inet_ntoa ( server->sin_addr ), server->sin_port ); return 0; } diff --git a/src/proto/tftm.c b/src/proto/tftm.c deleted file mode 100644 index fb011c6..0000000 --- a/src/proto/tftm.c +++ /dev/null @@ -1,208 +0,0 @@ -#include "etherboot.h" -#include "proto.h" -#include "errno.h" -#include "tftp.h" -#include "tftpcore.h" - -/** @file - * - * TFTM protocol - * - * TFTM is a protocol defined in RFC2090 as a multicast extension to - * TFTP. - */ - -static inline int tftm_process_opts ( struct tftp_state *state, - struct tftp_oack *oack ) { - struct in_addr old_mcast_addr = state->multicast.sin_addr; - - if ( ! tftp_process_opts ( state, oack ) ) - return 0; - - if ( old_mcast_addr.s_addr != state->multicast.sin_addr.s_addr ) { - if ( old_mcast_addr.s_addr ) { - DBG ( "TFTM: Leaving multicast group %@\n", - old_mcast_addr.s_addr ); - leave_group ( IGMP_SERVER ); - } - DBG ( "TFTM: Joining multicast group %@\n", - state->multicast.sin_addr.s_addr ); - join_group ( IGMP_SERVER, state->multicast.sin_addr.s_addr ); - } - - DBG ( "TFTM: I am a %s client\n", - ( state->master ? "master" : "slave" ) ); - - return 1; -} - - -static inline int tftm_process_data ( struct tftp_state *state, - struct tftp_data *data, - struct buffer *buffer ) { - unsigned int blksize; - off_t offset; - - /* Calculate block size and offset within file */ - blksize = ( ntohs ( data->udp.len ) - + offsetof ( typeof ( *data ), udp ) - - offsetof ( typeof ( *data ), data ) ); - offset = ( ntohs ( data->block ) - 1 ) * state->blksize; - - /* Check for oversized block */ - if ( blksize > state->blksize ) { - DBG ( "TFTM: oversized block size %d (max %d)\n", - blksize, state->blksize ); - errno = PXENV_STATUS_TFTP_INVALID_PACKET_SIZE; - return 0; - } - - /* Place block in the buffer */ - if ( ! fill_buffer ( buffer, data->data, offset, blksize ) ) { - DBG ( "TFTM: could not place data in buffer: %m\n" ); - return 0; - } - - /* If this is the last block, record the filesize (in case the - * server didn't supply a tsize option. - */ - if ( blksize < state->blksize ) { - state->tsize = offset + blksize; - } - - /* Record the last received block */ - state->block = ntohs ( data->block ); - - return 1; -} - - -static inline int tftm_next ( struct tftp_state *state, - union tftp_any **reply, - struct buffer *buffer ) { - long listen_timeout; - - listen_timeout = rfc2131_sleep_interval ( TIMEOUT, MAX_TFTP_RETRIES ); - - /* If we are not the master client, just listen for the next - * packet - */ - if ( ! state->master ) { - if ( tftp_get ( state, listen_timeout, reply ) ) { - /* Heard a non-error packet */ - return 1; - } - if ( *reply ) { - /* Received an error packet */ - return 0; - } - /* Didn't hear anything; try prodding the server */ - state->master = 1; - } - /* We are the master client; trigger the next packet - * that we want - */ - state->block = buffer->fill / state->blksize; - return tftp_ack ( state, reply ); -} - -/** - * Download a file via TFTM - * - * @v server TFTP server - * @v file File name - * @v buffer Buffer into which to load file - * @ret True File was downloaded successfully - * @ret False File was not downloaded successfully - * @err #PXENV_STATUS_TFTP_UNKNOWN_OPCODE Unknown type of TFTP block received - * @err other As returned by tftp_open() - * @err other As returned by tftp_process_opts() - * @err other As returned by tftp_ack() - * @err other As returned by tftp_process_data() - * - * Download a file from a TFTP server into the specified buffer using - * the TFTM protocol. - */ -static int tftm ( char *url __unused, struct sockaddr_in *server, char *file, - struct buffer *buffer ) { - struct tftp_state state; - union tftp_any *reply; - int rc = 0; - - /* Initialise TFTP state */ - memset ( &state, 0, sizeof ( state ) ); - state.server = *server; - - /* Start as the master. This means that if the TFTP server - * doesn't actually support multicast, we'll still ACK the - * packets and it should all proceed as for a normal TFTP - * connection. - */ - state.master = 1; - - /* Open the file */ - if ( ! tftp_open ( &state, file, &reply, 1 ) ) { - DBG ( "TFTM: could not open %@:%d/%s : %m\n", - server->sin_addr.s_addr, server->sin_port, file ); - return 0; - } - - /* Fetch file, a block at a time */ - while ( 1 ) { - twiddle(); - /* Process the current packet */ - switch ( ntohs ( reply->common.opcode ) ) { - case TFTP_OACK: - /* Options can be received at any time */ - if ( ! tftm_process_opts ( &state, &reply->oack ) ) { - DBG ( "TFTM: failed to process OACK: %m\n" ); - tftp_error ( &state, TFTP_ERR_BAD_OPTS, NULL ); - goto out; - } - break; - case TFTP_DATA: - if ( ! tftm_process_data ( &state, &reply->data, - buffer ) ) { - DBG ( "TFTM: failed to process DATA: %m\n" ); - tftp_error ( &state, TFTP_ERR_ILLEGAL_OP, - NULL ); - goto out; - } - break; - default: - DBG ( "TFTM: unexpected packet type %d\n", - ntohs ( reply->common.opcode ) ); - errno = PXENV_STATUS_TFTP_UNKNOWN_OPCODE; - tftp_error ( &state, TFTP_ERR_ILLEGAL_OP, NULL ); - goto out; - } - /* If we know the filesize, and we have all the data, stop */ - if ( state.tsize && ( buffer->fill == state.tsize ) ) - break; - /* Fetch the next packet */ - if ( ! tftm_next ( &state, &reply, buffer ) ) { - DBG ( "TFTM: could not get next block: %m\n" ); - if ( ! reply ) { - tftp_error ( &state, TFTP_ERR_ILLEGAL_OP, - NULL ); - } - goto out; - } - } - - /* ACK the final packet, as a courtesy to the server */ - tftp_ack_nowait ( &state ); - - rc = 1; - out: - if ( state.multicast.sin_addr.s_addr ) { - leave_group ( IGMP_SERVER ); - } - return rc; -} - -static struct protocol tftm_protocol __protocol = { - .name = "x-tftm", - .default_port = TFTP_PORT, - .load = tftm, -}; diff --git a/src/proto/tftpcore.c b/src/proto/tftpcore.c deleted file mode 100644 index c7673bd..0000000 --- a/src/proto/tftpcore.c +++ /dev/null @@ -1,541 +0,0 @@ -#include "tftp.h" -#include "old_tcp.h" /* for struct tcphdr */ -#include "errno.h" -#include "etherboot.h" -#include "tftpcore.h" - -/** @file */ - -/** - * await_reply() filter for TFTP packets - * - * @v ptr Pointer to a struct tftp_state - * @v tftp_state::server::sin_addr TFTP server IP address - * @v tftp_state::lport Client UDP port - * @v tftp_state::multicast::sin_addr Multicast IP address, or 0.0.0.0 - * @v tftp_state::multicast::sin_port Multicast UDP port, or 0 - * @v ip IP header - * @v udp UDP header - * @ret True This is our TFTP packet - * @ret False This is not one of our TFTP packets - * - * Wait for a TFTP packet that is part of the current connection - * (i.e. comes from the TFTP server, has the correct destination port, - * and is addressed either to our IP address and UDP port, or to our - * multicast listening address and UDP port). - * - * Use await_tftp() in code such as - * - * @code - * - * if ( await_reply ( await_tftp, 0, &tftp_state, timeout ) ) { - * ... - * } - * - * @endcode - */ -static int await_tftp ( int ival __unused, void *ptr, - unsigned short ptype __unused, struct iphdr *ip, - struct udphdr *udp, struct tcphdr *tcp __unused ) { - struct tftp_state *state = ptr; - - /* Must have valid UDP (and, therefore, also IP) headers */ - if ( ! udp ) { - DBG2 ( "TFTPCORE: not UDP\n" ); - return 0; - } - /* Packet must come from the TFTP server */ - if ( ip->src.s_addr != state->server.sin_addr.s_addr ) { - DBG2 ( "TFTPCORE: from %@, not from TFTP server %@\n", - ip->src.s_addr, state->server.sin_addr.s_addr ); - return 0; - } - /* Packet may be addressed to our IP address and unicast UDP - * port - */ - if ( ( ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr ) && - ( ntohs ( udp->dest ) == state->lport ) ) { - return 1; - } - /* Packet may be addressed to our multicast IP address and UDP - * port, if we have one - */ - if ( ( state->multicast.sin_addr.s_addr ) && - ( ip->dest.s_addr == state->multicast.sin_addr.s_addr ) && - ( ntohs ( udp->dest ) == state->multicast.sin_port ) ) { - return 1; - } - DBG2 ( "TFTPCORE: to %@:%d, not to %@:%d (or %@:%d)\n", - ip->dest.s_addr, ntohs ( udp->dest ), - arptable[ARP_CLIENT].ipaddr.s_addr, state->lport, - state->multicast.sin_addr.s_addr, state->multicast.sin_port ); - return 0; -} - -/** - * Retrieve a TFTP packet - * - * @v state TFTP transfer state - * @v tftp_state::server::sin_addr TFTP server IP address - * @v tftp_state::lport Client UDP port - * @v tftp_state::multicast::sin_addr Multicast IP address, or 0.0.0.0 - * @v tftp_state::multicast::sin_port Multicast UDP port, or 0 - * @v timeout Time to wait for a response - * @ret True Received a non-error response - * @ret False Received error response / no response - * @ret *reply The server's response, if any - * @err #PXENV_STATUS_TFTP_READ_TIMEOUT No response received in time - * @err other As set by tftp_set_errno() - * - * Retrieve the next packet sent by the TFTP server, if any is sent - * within the specified timeout period. The packet is returned via - * #reply. If no packet is received within the timeout period, a NULL - * value will be stored in #reply. - * - * If the response from the server is a TFTP ERROR packet, tftp_get() - * will return False and #errno will be set accordingly. - * - * You can differentiate between "received no response" and "received - * an error response" by checking #reply; if #reply is NULL then no - * response was received. - */ -int tftp_get ( struct tftp_state *state, long timeout, - union tftp_any **reply ) { - - *reply = NULL; - - if ( ! await_reply ( await_tftp, 0, state, timeout ) ) { - errno = PXENV_STATUS_TFTP_READ_TIMEOUT; - return 0; - } - - *reply = ( union tftp_any * ) &nic.packet[ETH_HLEN]; - DBG ( "TFTPCORE: got reply (type %d)\n", - ntohs ( (*reply)->common.opcode ) ); - if ( ntohs ( (*reply)->common.opcode ) == TFTP_ERROR ){ - tftp_set_errno ( &(*reply)->error ); - return 0; - } - return 1; -} - -/** - * Issue a TFTP open request (RRQ) - * - * @v state TFTP transfer state - * @v tftp_state::server::sin_addr TFTP server IP address - * @v tftp_state::server::sin_port TFTP server UDP port, or 0 - * @v tftp_state::lport Client UDP port, or 0 - * @v tftp_state::multicast::sin_addr Multicast IP address, or 0.0.0.0 - * @v tftp_state::multicast::sin_port Multicast UDP port, or 0 - * @v tftp_state::blksize Requested blksize, or 0 - * @v filename File name - * @v multicast Enable/disable rfc2090 multicast TFTP - * @ret True Received a non-error response - * @ret False Received error response / no response - * @ret tftp_state::server::sin_port TFTP server UDP port - * @ret tftp_state::lport Client UDP port - * @ret tftp_state::blksize Always #TFTP_DEFAULT_BLKSIZE - * @ret *reply The server's response, if any - * @err #PXENV_STATUS_TFTP_OPEN_TIMEOUT TFTP open timed out - * @err other As returned by udp_transmit() - * @err other As set by tftp_set_errno() - * - * Send a TFTP/TFTM/MTFTP RRQ (read request) to a TFTP server, and - * return the server's reply (which may be an OACK, DATA or ERROR - * packet). The server's reply will not be acknowledged, or processed - * in any way. - * - * If tftp_state::server::sin_port is 0, the standard TFTP server port - * (#TFTP_PORT) will be used. - * - * If tftp_state::lport is 0, the standard mechanism of - * using a new, unique port number for each TFTP request will be used. - * - * If tftp_state::multicast::sin_addr is not 0.0.0.0, it (and - * tftp_state::multicast::sin_port) will be used as a multicast - * listening address for replies from the TFTP server. - * - * For the various different types of TFTP server, you should treat - * tftp_state::lport and tftp_state::multicast as follows: - * - * - Standard TFTP server: set tftp_state::lport to 0, - * tftp_state::multicast::sin_addr to 0.0.0.0 and - * tftp_state::multicast::sin_port to 0. tftp_open() will set - * tftp_state::lport to the assigned local UDP port. - * - * - TFTM server: set tftp_state::lport to 0, - * tftp_state::multicast::sin_addr to 0.0.0.0 and - * tftp_state::multicast::sin_port to 0. tftp_open() will set - * tftp_state::lport to the assigned local UDP port. (Your call - * to tftp_process_opts() will then overwrite both - * tftp_state::multicast::sin_addr and - * tftp_state::multicast::sin_port with the values specified in - * the OACK packet.) - * - * - MTFTP server: set tftp_state::multicast::sin_addr to the - * multicast address and both tftp_state::lport and - * tftp_state::multicast::sin_port to the multicast port (both of - * which must be previously known, e.g. provided by a DHCP - * server). tftp_open() will not alter these values. - * - * If tftp_state::blksize is 0, the maximum blocksize - * (#TFTP_MAX_BLKSIZE) will be requested. - * - * On exit, tftp_state::blksize will always contain - * #TFTP_DEFAULT_BLKSIZE, since this is the blocksize value that must - * be assumed until the OACK packet is processed (by a subsequent call - * to tftp_process_opts()). - * - * tftp_state::server::sin_port will be set to the UDP port from which - * the server's response originated. This may or may not be the port - * to which the open request was sent. - * - * The options "blksize" and "tsize" will always be appended to a TFTP - * open request. The option "multicast" will be appended to the - * request if #multicast is True. Servers that do not understand any - * of these options should simply ignore them. - * - * tftp_open() will not automatically join or leave multicast groups; - * the caller is responsible for calling join_group() and - * leave_group() at appropriate times. - * - * If the response from the server is a TFTP ERROR packet, tftp_open() - * will return False and #errno will be set accordingly. - */ -int tftp_open ( struct tftp_state *state, const char *filename, - union tftp_any **reply, int multicast ) { - static unsigned short lport = 2000; /* local port */ - int fixed_lport; - struct tftp_rrq rrq; - char *p; - unsigned int rrqlen; - int retry; - - /* Flush receive queue */ - rx_qdrain(); - - /* Default to blksize of TFTP_MAX_BLKSIZE if none specified */ - if ( ! state->blksize ) - state->blksize = TFTP_MAX_BLKSIZE; - - /* Use default TFTP server port if none specified */ - if ( ! state->server.sin_port ) - state->server.sin_port = TFTP_PORT; - - /* Determine whether or not to use lport */ - fixed_lport = state->lport; - - /* Set up RRQ */ - rrq.opcode = htons ( TFTP_RRQ ); - p = rrq.data; - p += sprintf ( p, "%s%coctet%cblksize%c%d%ctsize%c0", - filename, 0, 0, 0, state->blksize, 0, 0 ) + 1; - if ( multicast ) { - p += sprintf ( p, "multicast%c", 0 ) + 1; - } - rrqlen = ( p - ( char * ) &rrq ); - - /* Set negotiated blksize to default value */ - state->blksize = TFTP_DEFAULT_BLKSIZE; - - /* Nullify received packet pointer */ - *reply = NULL; - - /* Transmit RRQ until we get a response */ - for ( retry = 0 ; retry < MAX_TFTP_RETRIES ; retry++ ) { - long timeout = rfc2131_sleep_interval ( TIMEOUT, retry ); - - /* Set client UDP port, if not already fixed */ - if ( ! fixed_lport ) - state->lport = ++lport; - - /* Send the RRQ */ - DBG ( "TFTPCORE: requesting %@:%d/%s from port %d\n", - state->server.sin_addr.s_addr, state->server.sin_port, - rrq.data, state->lport ); - if ( ! udp_transmit ( state->server.sin_addr.s_addr, - state->lport, state->server.sin_port, - rrqlen, &rrq ) ) - return 0; - - /* Wait for response */ - if ( tftp_get ( state, timeout, reply ) ) { - /* We got a non-error response */ - state->server.sin_port - = ntohs ( (*reply)->common.udp.src ); - DBG ( "TFTP server is at %@:%d\n", - state->server.sin_addr.s_addr, - state->server.sin_port ); - return 1; - } - if ( *reply ) { - /* We got an error response; abort */ - return 0; - } - } - - DBG ( "TFTPCORE: open request timed out\n" ); - errno = PXENV_STATUS_TFTP_OPEN_TIMEOUT; - return 0; -} - -/** - * Process a TFTP OACK packet - * - * @v state TFTP transfer state - * @v oack The TFTP OACK packet - * @ret True Options were processed successfully - * @ret False Options were not processed successfully - * @ret tftp_state::blksize Negotiated blksize - * @ret tftp_state::tsize File size (if known), or 0 - * @ret tftp_state::multicast::sin_addr Multicast IP address, or 0.0.0.0 - * @ret tftp_state::multicast::sin_port Multicast UDP port, or 0 - * @ret tftp_state::master Client is master - * @err EINVAL An invalid option value was encountered - * - * Process the options returned by the TFTP server in an rfc2347 OACK - * packet. The options "blksize" (rfc2348), "tsize" (rfc2349) and - * "multicast" (rfc2090) are recognised and processed; any other - * options are silently ignored. - * - * Where an option is not present in the OACK packet, the - * corresponding field(s) in #state will be left unaltered. - * - * Calling tftp_process_opts() does not send an acknowledgement for - * the OACK packet; this is the responsibility of the caller. - * - * @note If the "blksize" option is not present, tftp_state::blksize - * will @b not be implicitly set to #TFTP_DEFAULT_BLKSIZE. However, - * since tftp_open() always sets tftp_state::blksize to - * #TFTP_DEFAULT_BLKSIZE before returning, you probably don't need to - * worry about this. - */ -int tftp_process_opts ( struct tftp_state *state, struct tftp_oack *oack ) { - const char *p; - const char *end; - - DBG ( "TFTPCORE: processing OACK\n" ); - - /* End of options */ - end = ( ( char * ) &oack->udp ) + ntohs ( oack->udp.len ); - - /* Only possible error */ - errno = EINVAL; - - for ( p = oack->data ; p < end ; ) { - if ( strcasecmp ( "blksize", p ) == 0 ) { - p += 8; - state->blksize = strtoul ( p, &p, 10 ); - if ( *p ) { - DBG ( "TFTPCORE: garbage \"%s\" " - "after blksize\n", p ); - return 0; - } - p++; - DBG ( "TFTPCORE: got blksize %d\n", state->blksize ); - } else if ( strcasecmp ( "tsize", p ) == 0 ) { - p += 6; - state->tsize = strtoul ( p, &p, 10 ); - if ( *p ) { - DBG ( "TFTPCORE: garbage \"%s\" " - "after tsize\n", p ); - return 0; - } - p++; - DBG ( "TFTPCORE: got tsize %d\n", state->tsize ); - } else if ( strcasecmp ( "multicast", p ) == 0 ) { - p += 10; - char *e = strchr ( p, ',' ); - if ( ( ! e ) || ( e >= end ) ) { - DBG ( "TFTPCORE: malformed multicast field " - "\"%s\"\n", p ); - return 0; - } - /* IP address may be missing, in which case we - * should leave state->multicast.sin_addr - * unaltered. - */ - if ( e != p ) { - int rc; - *e = '\0'; - rc = inet_aton ( p, - &state->multicast.sin_addr ); - *e = ','; - if ( ! rc ) { - DBG ( "TFTPCORE: malformed multicast " - "IP address \"%s\"\n", p ); - return 0; - } - } - p = e + 1; - /* UDP port may also be missing */ - if ( *p != ',' ) { - state->multicast.sin_port - = strtoul ( p, &p, 10 ); - if ( *p != ',' ) { - DBG ( "TFTPCORE: garbage \"%s\" " - "after multicast port\n", p ); - return 0; - } - } - p++; - /* "Master Client" must always be present */ - state->master = strtoul ( p, &p, 10 ); - if ( *p ) { - DBG ( "TFTPCORE: garbage \"%s\" " - "after multicast mc\n", p ); - return 0; - } - p++; - DBG ( "TFTPCORE: got multicast %@:%d (%s)\n", - state->multicast.sin_addr.s_addr, - state->multicast.sin_port, - ( state->master ? "master" : "not master" ) ); - } else { - DBG ( "TFTPCORE: unknown option \"%s\"\n", p ); - p += strlen ( p ) + 1; /* skip option name */ - p += strlen ( p ) + 1; /* skip option value */ - } - } - - if ( p > end ) { - DBG ( "TFTPCORE: overran options in OACK\n" ); - return 0; - } - - return 1; -} - -/** - * Acknowledge a TFTP packet - * - * @v state TFTP transfer state - * @v tftp_state::server::sin_addr TFTP server IP address - * @v tftp_state::server::sin_port TFTP server UDP port - * @v tftp_state::lport Client UDP port - * @v tftp_state::block Most recently received block number - * @ret True Acknowledgement packet was sent - * @ret False Acknowledgement packet was not sent - * @err other As returned by udp_transmit() - * - * Send a TFTP ACK packet for the most recently received block. - * - * This sends only a single ACK packet; it does not wait for the - * server's response. - */ -int tftp_ack_nowait ( struct tftp_state *state ) { - struct tftp_ack ack; - - DBG ( "TFTPCORE: acknowledging data block %d\n", state->block ); - ack.opcode = htons ( TFTP_ACK ); - ack.block = htons ( state->block ); - return udp_transmit ( state->server.sin_addr.s_addr, - state->lport, state->server.sin_port, - sizeof ( ack ), &ack ); -} - -/** - * Acknowledge a TFTP packet and wait for a response - * - * @v state TFTP transfer state - * @v tftp_state::server::sin_addr TFTP server IP address - * @v tftp_state::server::sin_port TFTP server UDP port - * @v tftp_state::lport Client UDP port - * @v tftp_state::block Most recently received block number - * @ret True Received a non-error response - * @ret False Received error response / no response - * @ret *reply The server's response, if any - * @err #PXENV_STATUS_TFTP_READ_TIMEOUT Timed out waiting for a response - * @err other As returned by tftp_ack_nowait() - * @err other As set by tftp_set_errno() - * - * Send a TFTP ACK packet for the most recently received data block, - * and keep transmitting this ACK until we get a response from the - * server (e.g. a new data block). - * - * If the response is a TFTP DATA packet, no processing is done. - * Specifically, the block number is not checked to ensure that this - * is indeed the next data block in the sequence, nor is - * tftp_state::block updated with the new block number. - * - * If the response from the server is a TFTP ERROR packet, tftp_open() - * will return False and #errno will be set accordingly. - */ -int tftp_ack ( struct tftp_state *state, union tftp_any **reply ) { - int retry; - - *reply = NULL; - for ( retry = 0 ; retry < MAX_TFTP_RETRIES ; retry++ ) { - long timeout = rfc2131_sleep_interval ( TFTP_REXMT, retry ); - /* ACK the last data block */ - if ( ! tftp_ack_nowait ( state ) ) { - DBG ( "TFTP: could not send ACK: %m\n" ); - return 0; - } - if ( tftp_get ( state, timeout, reply ) ) { - /* We got a non-error response */ - return 1; - } - if ( *reply ) { - /* We got an error response */ - return 0; - } - } - DBG ( "TFTP: timed out during read\n" ); - errno = PXENV_STATUS_TFTP_READ_TIMEOUT; - return 0; -} - -/** - * Send a TFTP error - * - * @v state TFTP transfer state - * @v tftp_state::server::sin_addr TFTP server IP address - * @v tftp_state::server::sin_port TFTP server UDP port - * @v tftp_state::lport Client UDP port - * @v errcode TFTP error code - * @v errmsg Descriptive error string, or NULL - * @ret True Error packet was sent - * @ret False Error packet was not sent - * - * Send a TFTP ERROR packet back to the server to terminate the - * transfer. - * - * If #errmsg is NULL, the current error message string as returned by - * strerror(errno) will be used as the error text. - */ -int tftp_error ( struct tftp_state *state, int errcode, const char *errmsg ) { - struct tftp_error error; - - DBG ( "TFTPCORE: aborting with error %d (%s)\n", errcode, errmsg ); - error.opcode = htons ( TFTP_ERROR ); - error.errcode = htons ( errcode ); - strncpy ( error.errmsg, errmsg ? errmsg : strerror ( errno ), - sizeof ( error.errmsg ) ); - return udp_transmit ( state->server.sin_addr.s_addr, - state->lport, state->server.sin_port, - sizeof ( error ), &error ); -} - -/** - * Interpret a TFTP error - * - * @v error Pointer to a struct tftp_error - * - * Sets #errno based on the error code in a TFTP ERROR packet. - */ -void tftp_set_errno ( struct tftp_error *error ) { - static int errmap[] = { - [TFTP_ERR_FILE_NOT_FOUND] = PXENV_STATUS_TFTP_FILE_NOT_FOUND, - [TFTP_ERR_ACCESS_DENIED] = PXENV_STATUS_TFTP_ACCESS_VIOLATION, - [TFTP_ERR_ILLEGAL_OP] = PXENV_STATUS_TFTP_UNKNOWN_OPCODE, - }; - unsigned int errcode = ntohs ( error->errcode ); - - errno = 0; - if ( errcode < ( sizeof(errmap) / sizeof(errmap[0]) ) ) - errno = errmap[errcode]; - if ( ! errno ) - errno = PXENV_STATUS_TFTP_ERROR_OPCODE; -} |