Warnings purge
[people/xl0/gpxe.git] / src / proto / tftpcore.c
index da4399e..c7673bd 100644 (file)
@@ -1,30 +1,38 @@
 #include "tftp.h"
-#include "tcp.h" /* for struct tcphdr */
+#include "old_tcp.h" /* for struct tcphdr */
 #include "errno.h"
 #include "etherboot.h"
+#include "tftpcore.h"
 
-/** @file
- *
- * TFTP core functions
- *
- * This file provides functions that are common to the TFTP (rfc1350),
- * TFTM (rfc2090) and MTFTP (PXE) protocols.
- *
- */
+/** @file */
 
 /**
- * Wait for a TFTP packet
+ * await_reply() filter for TFTP packets
  *
- * @v ptr                      Pointer to a struct tftp_state
- * @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
+ * @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 or to our multicast
- * listening address).
+ * 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,
@@ -33,75 +41,143 @@ static int await_tftp ( int ival __unused, void *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 )
-               return 0;
-       /* Packet must be addressed to the correct UDP port */
-       if ( ntohs ( udp->dest ) != state->client.sin_port )
+       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 must be addressed to us, or to our multicast
-        * listening address (if we have one).
+       }
+       /* 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 ( ! ( ( ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr ) ||
-                ( ( state->client.sin_addr.s_addr ) && 
-                  ( ip->dest.s_addr == state->client.sin_addr.s_addr ) ) ) )
+       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 filename                         File name
  * @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::client::sin_addr     Client multicast IP address, or 0.0.0.0
- * @v tftp_state::client::sin_port     Client 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::client::sin_port   Client UDP port
- * @ret tftp_state::client::blksize    Always #TFTP_DEFAULT_BLKSIZE
- * @ret *tftp                          The server's response, if any
+ * @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
+ * If tftp_state::server::sin_port is 0, the standard TFTP server port
  * (#TFTP_PORT) will be used.
  *
- * If tftp_state::client::sin_addr is not 0.0.0.0, it will be used as
- * a multicast listening address for replies from the TFTP server.
- *
- * If tftp_state::client::sin_port is 0, the standard mechanism of
+ * 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::client as follows:
- *
- *   - Standard TFTP server: set tftp_state::client::sin_addr to
- *     0.0.0.0 and tftp_state::client::sin_port to 0.  tftp_open()
- *     will set tftp_state::client::sin_port to the assigned local UDP
- *     port.
- *
- *   - TFTM server: set tftp_state::client::sin_addr to 0.0.0.0 and
- *     tftp_state::client::sin_port to 0.  tftp_open() will set
- *     tftp_state::client::sin_port to the assigned local UDP port.
- *     (Your call to tftp_process_opts() will then overwrite both
- *     tftp_state::client::sin_addr and tftp_state::client::sin_port
- *     with the values return in the OACK packet.)
- *
- *   - MTFTP server: set tftp_state::client::sin_addr to the client
- *     multicast address and tftp_state::client::sin_port to the
- *     client multicast port (both of which must be previously known,
- *     e.g. provided by a DHCP server).  tftp_open() will not alter
- *     these values.
+ * 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.
@@ -111,20 +187,28 @@ static int await_tftp ( int ival __unused, void *ptr,
  * be assumed until the OACK packet is processed (by a subsequent call
  * to tftp_process_opts()).
  *
- * The options "blksize", "tsize" and "multicast" will always be
- * appended to a TFTP open request.  Servers that do not understand
- * any of these options should simply ignore them.
+ * 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 ( const char *filename, struct tftp_state *state,
-               union tftp_any **tftp ) {
+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;
 
@@ -140,21 +224,23 @@ int tftp_open ( const char *filename, struct tftp_state *state,
                state->server.sin_port = TFTP_PORT;
 
        /* Determine whether or not to use lport */
-       fixed_lport = state->server.sin_port;
+       fixed_lport = state->lport;
 
        /* Set up RRQ */
        rrq.opcode = htons ( TFTP_RRQ );
-       rrqlen = ( offsetof ( typeof ( rrq ), data ) +
-                   sprintf ( rrq.data,
-                             "%s%coctet%cblksize%c%d%ctsize%c0%cmulticast%c",
-                             filename, 0, 0, 0, state->blksize, 0, 0, 0, 0 )
-                   + 1 );
+       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 */
-       *tftp = NULL;
+       *reply = NULL;
 
        /* Transmit RRQ until we get a response */
        for ( retry = 0 ; retry < MAX_TFTP_RETRIES ; retry++ ) {
@@ -162,22 +248,34 @@ int tftp_open ( const char *filename, struct tftp_state *state,
 
                /* Set client UDP port, if not already fixed */
                if ( ! fixed_lport )
-                       state->client.sin_port = ++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->client.sin_port,
-                                     state->server.sin_port,
+                                     state->lport, state->server.sin_port,
                                      rrqlen, &rrq ) )
                        return 0;
                
                /* Wait for response */
-               if ( await_reply ( await_tftp, 0, state, timeout ) ) {
-                       *tftp = ( union tftp_any * ) &nic.packet[ETH_HLEN];
+               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;
 }
@@ -185,14 +283,14 @@ int tftp_open ( const char *filename, struct tftp_state *state,
 /**
  * Process a TFTP OACK packet
  *
- * @v oack                             The 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::client::sin_addr   Client multicast IP address, or 0.0.0.0
- * @ret tftp_state::client::sin_port   Client UDP port
+ * @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
  *
@@ -213,10 +311,12 @@ int tftp_open ( const char *filename, struct tftp_state *state,
  * #TFTP_DEFAULT_BLKSIZE before returning, you probably don't need to
  * worry about this.
  */
-int tftp_process_opts ( struct tftp_oack *oack, struct tftp_state *state ) {
+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 );
 
@@ -233,6 +333,7 @@ int tftp_process_opts ( struct tftp_oack *oack, struct tftp_state *state ) {
                                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 );
@@ -242,7 +343,9 @@ int tftp_process_opts ( struct tftp_oack *oack, struct tftp_state *state ) {
                                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 "
@@ -250,13 +353,14 @@ int tftp_process_opts ( struct tftp_oack *oack, struct tftp_state *state ) {
                                return 0;
                        }
                        /* IP address may be missing, in which case we
-                        * should leave state->client.sin_addr
+                        * should leave state->multicast.sin_addr
                         * unaltered.
                         */
                        if ( e != p ) {
                                int rc;
                                *e = '\0';
-                               rc = inet_aton ( p, &state->client.sin_addr );
+                               rc = inet_aton ( p,
+                                                &state->multicast.sin_addr );
                                *e = ',';
                                if ( ! rc ) {
                                        DBG ( "TFTPCORE: malformed multicast "
@@ -267,15 +371,15 @@ int tftp_process_opts ( struct tftp_oack *oack, struct tftp_state *state ) {
                        p = e + 1;
                        /* UDP port may also be missing */
                        if ( *p != ',' ) {
-                               state->client.sin_port = strtoul ( p, &p, 10 );
+                               state->multicast.sin_port
+                                       = strtoul ( p, &p, 10 );
                                if ( *p != ',' ) {
                                        DBG ( "TFTPCORE: garbage \"%s\" "
                                              "after multicast port\n", p );
                                        return 0;
                                }
-                       } else {
-                               p++;
                        }
+                       p++;
                        /* "Master Client" must always be present */
                        state->master = strtoul ( p, &p, 10 );
                        if ( *p ) {
@@ -284,7 +388,12 @@ int tftp_process_opts ( struct tftp_oack *oack, struct tftp_state *state ) {
                                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 */
                }
@@ -297,3 +406,136 @@ int tftp_process_opts ( struct tftp_oack *oack, struct tftp_state *state ) {
 
        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;
+}