diff options
Diffstat (limited to 'cfe/cfe/net/net_tftp.c')
-rw-r--r-- | cfe/cfe/net/net_tftp.c | 921 |
1 files changed, 921 insertions, 0 deletions
diff --git a/cfe/cfe/net/net_tftp.c b/cfe/cfe/net/net_tftp.c new file mode 100644 index 0000000..1c3e799 --- /dev/null +++ b/cfe/cfe/net/net_tftp.c @@ -0,0 +1,921 @@ +/* ********************************************************************* + * Broadcom Common Firmware Environment (CFE) + * + * TFTP Client File: net_tftp.c + * + * This module contains a TFTP client. + * + * Author: Mitch Lichtenberg (mpl@broadcom.com) + * + ********************************************************************* + * + * Copyright 2000,2001,2002,2003 + * Broadcom Corporation. All rights reserved. + * + * This software is furnished under license and may be used and + * copied only in accordance with the following terms and + * conditions. Subject to these conditions, you may download, + * copy, install, use, modify and distribute modified or unmodified + * copies of this software in source and/or binary form. No title + * or ownership is transferred hereby. + * + * 1) Any source code used, modified or distributed must reproduce + * and retain this copyright notice and list of conditions + * as they appear in the source file. + * + * 2) No right is granted to use any trade name, trademark, or + * logo of Broadcom Corporation. The "Broadcom Corporation" + * name may not be used to endorse or promote products derived + * from this software without the prior written permission of + * Broadcom Corporation. + * + * 3) THIS SOFTWARE IS PROVIDED "AS-IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, ANY IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT + * SHALL BROADCOM BE LIABLE FOR ANY DAMAGES WHATSOEVER, AND IN + * PARTICULAR, BROADCOM SHALL NOT BE LIABLE FOR DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE), EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ********************************************************************* */ + + +#include "lib_types.h" +#include "lib_string.h" +#include "lib_queue.h" +#include "lib_malloc.h" +#include "lib_printf.h" + +#include "cfe_error.h" +#include "cfe_fileops.h" + +/* Foxconn add start by Cliff Wang, 03/23/2010 */ +#include "cfe_console.h" +/* Foxconn add end by Cliff Wang, 03/23/2010 */ + +#include "net_ebuf.h" +#include "net_ether.h" + +#include "cfe.h" + +#include "cfe_loader.h" + +#include "net_api.h" + + + +/* ********************************************************************* + * TFTP protocol + ********************************************************************* */ + +#define UDP_PROTO_TFTP 69 + +#define TFTP_BLOCKSIZE 512 + +#define TFTP_OP_RRQ 1 +#define TFTP_OP_WRQ 2 +#define TFTP_OP_DATA 3 +#define TFTP_OP_ACK 4 +#define TFTP_OP_ERROR 5 + +#define TFTP_ERR_DISKFULL 3 + +#define TFTP_MAX_RETRIES 8 + +#define TFTP_RRQ_TIMEOUT 1 /* seconds */ +#define TFTP_RECV_TIMEOUT 1 /* seconds */ + +/* ********************************************************************* + * TFTP context + ********************************************************************* */ + +typedef struct tftp_fsctx_s { + int dummy; +} tftp_fsctx_t; + +typedef struct tftp_info_s { + int tftp_socket; + uint8_t tftp_data[TFTP_BLOCKSIZE]; + int tftp_blklen; + int tftp_blkoffset; + int tftp_fileoffset; + uint16_t tftp_blknum; + uint8_t tftp_ipaddr[IP_ADDR_LEN]; + int tftp_lastblock; + int tftp_error; + int tftp_filemode; + char *tftp_filename; +} tftp_info_t; + +/* ********************************************************************* + * Prototypes + ********************************************************************* */ + +static int tftp_fileop_init(void **fsctx,void *devicename); +static int tftp_fileop_open(void **ref,void *fsctx,char *filename,int mode); +static int tftp_fileop_read(void *ref,uint8_t *buf,int len); +static int tftp_fileop_write(void *ref,uint8_t *buf,int len); +static int tftp_fileop_seek(void *ref,int offset,int how); +static void tftp_fileop_close(void *ref); +static void tftp_fileop_uninit(void *fsctx); + +/* Foxconn add start by Cliff Wang, 03/23/2010 */ +int tftp_max_retries = TFTP_MAX_RETRIES; +int tftp_rrq_timeout = TFTP_RRQ_TIMEOUT; +int tftp_recv_timeout = TFTP_RECV_TIMEOUT; +/* Foxconn add end by Cliff Wang, 03/23/2010 */ + +/* ********************************************************************* + * TFTP fileio dispatch table + ********************************************************************* */ + +const fileio_dispatch_t tftp_fileops = { + "tftp", + LOADFLG_NOBB | FSYS_TYPE_NETWORK, + tftp_fileop_init, + tftp_fileop_open, + tftp_fileop_read, + tftp_fileop_write, + tftp_fileop_seek, + tftp_fileop_close, + tftp_fileop_uninit +}; + + +/* ********************************************************************* + * _tftp_open(info,hostname,filename,mode) + * + * Open a file on a remote host, using the TFTP protocol. + * + * Input parameters: + * info - TFTP information + * hostname - host name or IP address of remote host + * filename - name of file on remote system + * mode - file open mode, read or write + * + * Return value: + * 0 if ok + * else error code + ********************************************************************* */ + +static int _tftp_open(tftp_info_t *info,char *hostname,char *filename,int mode) +{ + ebuf_t *buf = NULL; + const char *datamode = "octet"; + uint16_t type,error,block; + int res; + int retries; + + /* + * Look up the remote host's IP address + */ + + res = dns_lookup(hostname,info->tftp_ipaddr); + if (res < 0) return res; + + /* + * Open a UDP socket to the TFTP server + */ + + info->tftp_socket = udp_socket(UDP_PROTO_TFTP); + info->tftp_lastblock = 0; + info->tftp_error = 0; + info->tftp_filemode = mode; + + /* + * Try to send the RRQ packet to open the file + */ + + for (retries = 0; retries < TFTP_MAX_RETRIES; retries++) { + + buf = udp_alloc(); + if (!buf) break; + + if (info->tftp_filemode == FILE_MODE_READ) { + ebuf_append_u16_be(buf,TFTP_OP_RRQ); /* read file */ + } + else { + ebuf_append_u16_be(buf,TFTP_OP_WRQ); /* write file */ + } + ebuf_append_bytes(buf,filename,strlen(filename)+1); + ebuf_append_bytes(buf,datamode,strlen(datamode)+1); + + udp_send(info->tftp_socket,buf,info->tftp_ipaddr); + + buf = udp_recv_with_timeout(info->tftp_socket,TFTP_RRQ_TIMEOUT); + if (buf) break; + } + + /* + * If we got no response, bail now. + */ + + if (!buf) { + udp_close(info->tftp_socket); + info->tftp_socket = -1; + return CFE_ERR_TIMEOUT; + } + + /* + * Otherwise, process the response. + */ + + ebuf_get_u16_be(buf,type); + + switch (type) { + case TFTP_OP_ACK: + /* + * Acks are what we get back on a WRQ command, + * but are otherwise unexpected. + */ + if (info->tftp_filemode == FILE_MODE_WRITE) { + udp_connect(info->tftp_socket,(uint16_t) buf->eb_usrdata); + info->tftp_blknum = 1; + info->tftp_blklen = 0; + udp_free(buf); + return 0; + break; + } + /* fall through */ + case TFTP_OP_RRQ: + case TFTP_OP_WRQ: + default: + /* + * we aren't expecting any of these messages + */ + udp_free(buf); + udp_close(info->tftp_socket); + info->tftp_socket = -1; + return CFE_ERR_PROTOCOLERR; + + case TFTP_OP_ERROR: + /* + * Process the error return (XXX: remove xprintf here) + */ + ebuf_get_u16_be(buf,error); + xprintf("TFTP error %d: %s\n",error,ebuf_ptr(buf)); + udp_free(buf); + udp_close(info->tftp_socket); + info->tftp_socket = -1; + return CFE_ERR_PROTOCOLERR; + + case TFTP_OP_DATA: + /* + * Yay, we've got data! Store the first block. + */ + ebuf_get_u16_be(buf,block); + udp_connect(info->tftp_socket,(uint16_t) buf->eb_usrdata); + info->tftp_blknum = block; + info->tftp_blklen = ebuf_length(buf); + ebuf_get_bytes(buf,info->tftp_data,ebuf_length(buf)); + udp_free(buf); + if (info->tftp_blklen < TFTP_BLOCKSIZE) { + info->tftp_lastblock = 1; /* EOF */ + } + return 0; + break; + + } +} + + +/* ********************************************************************* + * _tftp_readmore(info) + * + * Read the next block of the file. We do this by acking the + * previous block. Once that block is acked, the TFTP server + * should send the next block to us. + * + * Input parameters: + * info - TFTP information + * + * Return value: + * 0 if ok + * else error code + ********************************************************************* */ + +static int _tftp_readmore(tftp_info_t *info) +{ + ebuf_t *buf; + uint16_t cmd,block; + int retries; + + /* + * If we've already read the last block, there is no more + */ + + if (info->tftp_lastblock) return 1; + if (info->tftp_error) return CFE_ERR_TIMEOUT; + + /* + * Otherwise, ack the current block so another one will come + */ + + for (retries = 0; retries < TFTP_MAX_RETRIES; retries++) { + + buf = udp_alloc(); + if (!buf) return -1; + + /* + * Send the ack + */ + + ebuf_append_u16_be(buf,TFTP_OP_ACK); + ebuf_append_u16_be(buf,info->tftp_blknum); + udp_send(info->tftp_socket,buf,info->tftp_ipaddr); + + /* + * Wait for some response, retransmitting as necessary + */ + + /* Foxconn add start by Cliff Wang, 03/23/2010 */ + buf = udp_recv_with_timeout(info->tftp_socket,tftp_recv_timeout); + // buf = udp_recv_with_timeout(info->tftp_socket,TFTP_RECV_TIMEOUT); + /* Foxconn add end by Cliff Wang, 03/23/2010 */ + + if (buf == NULL) continue; + + /* + * Got a response, make sure it's right + */ + + ebuf_get_u16_be(buf,cmd); + if (cmd != TFTP_OP_DATA) { + udp_free(buf); + continue; + } + + ebuf_get_u16_be(buf,block); + if (block != (info->tftp_blknum+1)) { + udp_free(buf); + continue; + } + + /* + * It's the correct response. Copy the user data + */ + + info->tftp_blknum = block; + info->tftp_blklen = ebuf_length(buf); + ebuf_get_bytes(buf,info->tftp_data,ebuf_length(buf)); + udp_free(buf); + break; + } + + /* + * If the block is less than 512 bytes long, it's the EOF block. + */ + + if (retries == TFTP_MAX_RETRIES) { + info->tftp_error = 1; + return CFE_ERR_TIMEOUT; + } + + if (info->tftp_blklen < TFTP_BLOCKSIZE) { + info->tftp_lastblock = 1; /* EOF */ + } + + return 0; +} + +/* ********************************************************************* + * _tftp_writemore(info) + * + * Write the next block of the file, sending the data from our + * holding buffer. Note that the holding buffer must be full + * or else we consider this to be an EOF. + * + * Input parameters: + * info - TFTP information + * + * Return value: + * 0 if ok + * else error code + ********************************************************************* */ + +static int _tftp_writemore(tftp_info_t *info) +{ + ebuf_t *buf; + uint16_t cmd,block,error; + int retries; + + /* + * If we've already written the last block, there is no more + */ + + if (info->tftp_lastblock) return 1; + if (info->tftp_error) return CFE_ERR_TIMEOUT; + + /* + * Otherwise, send a block + */ + + for (retries = 0; retries < TFTP_MAX_RETRIES; retries++) { + + buf = udp_alloc(); + if (!buf) return -1; + + /* + * Send the data + */ + + ebuf_append_u16_be(buf,TFTP_OP_DATA); + ebuf_append_u16_be(buf,info->tftp_blknum); + ebuf_append_bytes(buf,info->tftp_data,info->tftp_blklen); + udp_send(info->tftp_socket,buf,info->tftp_ipaddr); + + /* + * Wait for some response, retransmitting as necessary + */ + + /* Foxconn add start by Cliff Wang, 03/23/2010 */ + buf = udp_recv_with_timeout(info->tftp_socket, tftp_recv_timeout); + // buf = udp_recv_with_timeout(info->tftp_socket,TFTP_RECV_TIMEOUT); + /* Foxconn add end by Cliff Wang, 03/23/2010 */ + + if (buf == NULL) continue; + + /* + * Got a response, make sure it's right + */ + + ebuf_get_u16_be(buf,cmd); + + if (cmd == TFTP_OP_ERROR) { + /* + * Process the error return (XXX: remove xprintf here) + */ + ebuf_get_u16_be(buf,error); + xprintf("TFTP write error %d: %s\n",error,ebuf_ptr(buf)); + info->tftp_error = 1; + info->tftp_lastblock = 1; + udp_free(buf); + return CFE_ERR_IOERR; + } + + if (cmd != TFTP_OP_ACK) { + udp_free(buf); + continue; + } + + ebuf_get_u16_be(buf,block); + if (block != (info->tftp_blknum)) { + udp_free(buf); + continue; + } + + /* + * It's the correct response. Update the block # + */ + + info->tftp_blknum++; + if (info->tftp_blklen != TFTP_BLOCKSIZE) info->tftp_lastblock = 1; + udp_free(buf); + break; + } + + /* + * If we had some failure, mark the stream with an error + */ + + if (retries == TFTP_MAX_RETRIES) { + info->tftp_error = 1; + return CFE_ERR_TIMEOUT; + } + + return 0; +} + +/* Foxconn add start by Cliff Wang, 03/23/2010 */ +static int _tftpd_open(tftp_info_t *info,char *hostname,char *filename,int mode) +{ + ebuf_t *buf = NULL; + uint16_t type; + int res; + int retries; + char ch = 0; + + /* + * * Open a UDP socket + * */ + + info->tftp_socket = udp_socket(UDP_PROTO_TFTP); + info->tftp_lastblock = 0; + info->tftp_error = 0; + info->tftp_filemode = mode; + + /* + * * Listen for requests + * */ + + res = udp_bind(info->tftp_socket,UDP_PROTO_TFTP); + if (res < 0) return res; + + res = CFE_ERR_TIMEOUT; + + for (retries = 0; retries < tftp_max_retries; retries++) { + while (console_status()) { + console_read(&ch,1); + if (ch == 3) break; + } + + if (ch == 3) { + res = CFE_ERR_INTR; + break; + } + + buf = udp_recv_with_timeout(info->tftp_socket,tftp_recv_timeout); + if (buf == NULL) continue; + + /* + * * Process the request + * */ + + ebuf_get_u16_be(buf,type); + + switch (type) { + case TFTP_OP_RRQ: + udp_connect(info->tftp_socket,(uint16_t) buf->eb_usrdata); + memcpy(info->tftp_ipaddr,buf->eb_usrptr,IP_ADDR_LEN); + info->tftp_blknum = 1; + info->tftp_blklen = 0; + res = 0; + break; + + case TFTP_OP_WRQ: + udp_connect(info->tftp_socket,(uint16_t) buf->eb_usrdata); + memcpy(info->tftp_ipaddr,buf->eb_usrptr,IP_ADDR_LEN); + info->tftp_blknum = 0; + res = _tftp_readmore(info); + break; + + default: + /* + * aren't expecting any of these messages + * */ + res = CFE_ERR_PROTOCOLERR; + break; + } + + udp_free(buf); + break; + } + + if (res) { + udp_close(info->tftp_socket); + info->tftp_socket = -1; + } + + return res; +} + + +/* ********************************************************************* + * _tftp_close(info) + * + * Close a TFTP file. There are two cases for what we do + * here. If we're closing the file mid-stream, send an error + * packet to tell the host we're getting out early. Otherwise, + * just ack the final packet and the connection will close + * gracefully. + * + * Input parameters: + * info - TFTP info + * + * Return value: + * 0 if ok, else error code + ********************************************************************* */ + +static int _tftp_close(tftp_info_t *info) +{ + ebuf_t *buf; + const char *emsg = "transfer cancelled"; /* some error message */ + + if (info->tftp_socket == -1) return 0; + + if (info->tftp_filemode == FILE_MODE_READ) { + buf = udp_alloc(); + if (buf) { + /* If we're on the EOF packet, just send an ack */ + if (info->tftp_lastblock) { + ebuf_append_u16_be(buf,TFTP_OP_ACK); + ebuf_append_u16_be(buf,info->tftp_blknum); + } + else { + ebuf_append_u16_be(buf,TFTP_OP_ERROR); + ebuf_append_u16_be(buf,TFTP_ERR_DISKFULL); + ebuf_append_bytes(buf,emsg,strlen(emsg)+1); + } + udp_send(info->tftp_socket,buf,info->tftp_ipaddr); + } + } + else { + /* Just flush out the remaining write data, if any */ + _tftp_writemore(info); + } + + udp_close(info->tftp_socket); + info->tftp_socket = -1; + return 0; +} + + + +/* ********************************************************************* + * tftp_fileop_init(fsctx,device) + * + * Create a file system device context for the TFTP service + * + * Input parameters: + * fsctx - location to place context information + * device - underlying device (unused) + * + * Return value: + * 0 if ok + * else error code + ********************************************************************* */ + +static int tftp_fileop_init(void **fsctx,void *dev) +{ + void *ref; + + ref = KMALLOC(sizeof(tftp_fsctx_t),0); + + if (!ref) return CFE_ERR_NOMEM; + *fsctx = ref; + return 0; +} + +/* ********************************************************************* + * tftp_fileop_open(ref,fsctx,filename,mode) + * + * This is the filesystem entry point for opening a TFTP file. + * Allocate a tftp_info structure, open the TFTP file, and + * return a handle. + * + * Input parameters: + * ref - location to place reference data (the TFTP structure) + * fsctx - filesystem context + * filename - name of remote file to open + * mode - FILE_MODE_READ or FILE_MODE_WRITE + * + * Return value: + * 0 if ok + * else error code + ********************************************************************* */ + +static int tftp_fileop_open(void **ref,void *fsctx,char *filename,int mode) +{ + tftp_info_t *info; + char *host; + char *file; + int res; + + if ((mode != FILE_MODE_READ) && (mode != FILE_MODE_WRITE)) { + /* must be either read or write, not both */ + return CFE_ERR_UNSUPPORTED; + } + + /* Allocate the tftp info structure */ + + info = KMALLOC(sizeof(tftp_info_t),0); + if (!info) { + return CFE_ERR_NOMEM; + } + + + info->tftp_filename = lib_strdup(filename); + if (!info->tftp_filename) { + KFREE(info); + return CFE_ERR_NOMEM; + } + + lib_chop_filename(info->tftp_filename,&host,&file); + + /* Open the file */ + /* Foxconn add start by Cliff Wang, 03/23/2010 */ + if (!*host && !*file) { + /* TFTP server if hostname and filename are not specified */ + res = _tftpd_open(info,host,file,mode); + } else { + res = _tftp_open(info,host,file,mode); + } + /* Foxconn add end by Cliff Wang, 03/23/2010 */ + + if (res == 0) { + info->tftp_blkoffset = 0; + info->tftp_fileoffset = 0; + *ref = info; + } + else { + KFREE(info->tftp_filename); + KFREE(info); + *ref = NULL; + } + return res; +} + +/* ********************************************************************* + * tftp_fileop_read(ref,buf,len) + * + * Read some bytes from the remote TFTP file. Do this by copying + * data from the block buffer, and reading more data from the + * remote file as necessary. + * + * Input parameters: + * ref - tftp_info structure + * buf - destination buffer address (NULL to read data and + * not copy it anywhere) + * len - number of bytes to read + * + * Return value: + * number of bytes read, 0 for EOF, <0 if an error occured. + ********************************************************************* */ + +static int tftp_fileop_read(void *ref,uint8_t *buf,int len) +{ + tftp_info_t *info = (tftp_info_t *) ref; + int copied = 0; + int amtcopy; + int res; + + if (info->tftp_error) return CFE_ERR_IOERR; + if (info->tftp_filemode == FILE_MODE_WRITE) return CFE_ERR_UNSUPPORTED; + + while (len) { + + if (info->tftp_blkoffset >= info->tftp_blklen) break; + amtcopy = len; + + if (amtcopy > (info->tftp_blklen-info->tftp_blkoffset)) { + amtcopy = (info->tftp_blklen-info->tftp_blkoffset); + } + + if (buf) { + memcpy(buf,&(info->tftp_data[info->tftp_blkoffset]),amtcopy); + buf += amtcopy; + } + + info->tftp_blkoffset += amtcopy; + len -= amtcopy; + info->tftp_fileoffset += amtcopy; + copied += amtcopy; + + if (info->tftp_blkoffset >= info->tftp_blklen) { + res = _tftp_readmore(info); + if (res != 0) break; + info->tftp_blkoffset = 0; + } + } + + return copied; +} + +/* ********************************************************************* + * tftp_fileop_write(ref,buf,len) + * + * Write some bytes to the remote TFTP file. Do this by copying + * data to the block buffer, and writing data to the + * remote file as necessary. + * + * Input parameters: + * ref - tftp_info structure + * buf - source buffer address + * len - number of bytes to write + * + * Return value: + * number of bytes written, 0 for EOF, <0 if an error occured. + ********************************************************************* */ + +static int tftp_fileop_write(void *ref,uint8_t *buf,int len) +{ + tftp_info_t *info = (tftp_info_t *) ref; + int copied = 0; + int amtcopy; + int res; + + if (info->tftp_error) return CFE_ERR_IOERR; + if (info->tftp_filemode == FILE_MODE_READ) return CFE_ERR_UNSUPPORTED; + + while (len) { + + amtcopy = len; + if (amtcopy > (TFTP_BLOCKSIZE - info->tftp_blklen)) { + amtcopy = (TFTP_BLOCKSIZE - info->tftp_blklen); + } + + memcpy(&(info->tftp_data[info->tftp_blklen]),buf,amtcopy); + buf += amtcopy; + + info->tftp_blklen += amtcopy; + len -= amtcopy; + info->tftp_fileoffset += amtcopy; + copied += amtcopy; + + if (info->tftp_blklen == TFTP_BLOCKSIZE) { + res = _tftp_writemore(info); + if (res != 0) { + break; + } + info->tftp_blklen = 0; + } + } + + return copied; +} + +/* ********************************************************************* + * tftp_fileop_seek(ref,offset,how) + * + * Seek within a TFTP file. Note that you can only seek *forward*, + * as TFTP doesn't really let you go backwards. (I suppose you + * could reopen the file, but thus far nobody needs to go + * backwards). You can only seek in a file in read mode. + * + * Input parameters: + * ref - our tftp information + * offset - distance to move + * how - how to move, (FILE_SEEK_*) + * + * Return value: + * new offset, or <0 if an error occured. + ********************************************************************* */ + +static int tftp_fileop_seek(void *ref,int offset,int how) +{ + tftp_info_t *info = (tftp_info_t *) ref; + int delta; + int startloc; + int res; + + if (info->tftp_filemode == FILE_MODE_WRITE) return CFE_ERR_UNSUPPORTED; + + switch (how) { + case FILE_SEEK_BEGINNING: + startloc = info->tftp_fileoffset; + break; + case FILE_SEEK_CURRENT: + startloc = 0; + break; + default: + startloc = 0; + break; + } + + delta = offset - startloc; + if (delta < 0) { + xprintf("Warning: negative seek on tftp file attempted\n"); + return CFE_ERR_UNSUPPORTED; + } + res = tftp_fileop_read(ref,NULL,delta); + if (res < 0) return res; + + return info->tftp_fileoffset; +} + + +/* ********************************************************************* + * tftp_fileop_close(ref) + * + * Close the TFTP file. + * + * Input parameters: + * ref - our TFTP info + * + * Return value: + * nothing + ********************************************************************* */ + +static void tftp_fileop_close(void *ref) +{ + tftp_info_t *info = (tftp_info_t *) ref; + + _tftp_close(info); + + KFREE(info->tftp_filename); + KFREE(info); +} + + +/* ********************************************************************* + * tftp_fileop_uninit(fsctx) + * + * Uninitialize the filesystem context, freeing allocated + * resources. + * + * Input parameters: + * fsctx - our context + * + * Return value: + * nothing + ********************************************************************* */ + +static void tftp_fileop_uninit(void *fsctx) +{ + KFREE(fsctx); +} |