apps/netutils/tftpc: This commit modifies the TFTP client functions to use a data read/write callback instead of a file.This allows TFTP to write to arbitrary destination (in my case, a MTD device - for firmware update). Two new functions are introduced for this, named tftpget_cb and tftpput_cb. They are just made of most of the existing code. The previously existing tftpget/tftpput functions are now wrappers on the new ones, with callbacks that read/write from files, so my modifications are backwards compatible with existing applications, eg the associated nsh commands dont need to be changed.
This commit is contained in:
parent
0b3ce51a0e
commit
3211ab9069
@ -1,8 +1,10 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* apps/include/netutils/tftp.h
|
* apps/include/netutils/tftp.h
|
||||||
*
|
*
|
||||||
* Copyright (C) 2008-2009, 2011 Gregory Nutt. All rights reserved.
|
* Copyright (C) 2008-2009, 2011, 2018 Gregory Nutt. All rights reserved.
|
||||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||||
|
* Copyright (C) 2018 Sebastien Lorquet. All rights reserved.
|
||||||
|
* Author: Sebastien Lorquet <sebastien@lorquet.fr>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@ -40,17 +42,38 @@
|
|||||||
* Included Files
|
* Included Files
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <nuttx/config.h>
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Pre-processor Definitions
|
* Public Type Definitions
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Public Type Definitions
|
* Name: tftp_callback_t
|
||||||
|
*
|
||||||
|
* Description: This callback type is used for data exchange with the tftp
|
||||||
|
* protocol handler.
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* ctx - pointer passed to the get or put TFTP function
|
||||||
|
* offset - GET: Always zero
|
||||||
|
* PUT: Data offset within the transmitted file
|
||||||
|
* buf - GET: Pointer to the received data
|
||||||
|
* PUT: Location of data buffer that will be transferred
|
||||||
|
* len - GET: Size of the received data (usually 512)
|
||||||
|
* PUT: Size of the provided buffer
|
||||||
|
* Return value:
|
||||||
|
* GET: Number of bytes that were written to the destination by the user
|
||||||
|
* PUT: Number of bytes that were retrieved from the user data source
|
||||||
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
typedef ssize_t (*tftp_callback_t)(FAR void *ctx, uint32_t offset,
|
||||||
|
FAR uint8_t *buf, size_t len);
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Public Function Prototypes
|
* Public Function Prototypes
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@ -63,8 +86,18 @@ extern "C"
|
|||||||
#define EXTERN extern
|
#define EXTERN extern
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int tftpget(const char *remote, const char *local, in_addr_t addr, bool binary);
|
int tftpget_cb(FAR const char *remote, in_addr_t addr, bool binary,
|
||||||
int tftpput(const char *local, const char *remote, in_addr_t addr, bool binary);
|
tftp_callback_t cb, FAR void *ctx);
|
||||||
|
|
||||||
|
int tftpput_cb(FAR const char *remote, in_addr_t addr, bool binary,
|
||||||
|
tftp_callback_t cb, FAR void *ctx);
|
||||||
|
|
||||||
|
#if CONFIG_NFILE_DESCRIPTORS > 0
|
||||||
|
int tftpget(FAR const char *remote, FAR const char *local, in_addr_t addr,
|
||||||
|
bool binary);
|
||||||
|
int tftpput(FAR const char *local, FAR const char *remote, in_addr_t addr,
|
||||||
|
bool binary);
|
||||||
|
#endif
|
||||||
|
|
||||||
#undef EXTERN
|
#undef EXTERN
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* netuils/tftp/tftpc_get.c
|
* netutils/tftp/tftpc_get.c
|
||||||
*
|
*
|
||||||
* Copyright (C) 2008-2009, 2011 Gregory Nutt. All rights reserved.
|
* Copyright (C) 2008-2009, 2011, 2018 Gregory Nutt. All rights reserved.
|
||||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||||
|
* Copyright (C) 2018 Sebastien Lorquet. All rights reserved.
|
||||||
|
* Author: Sebastien Lorquet <sebastien@lorquet.fr>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@ -56,7 +58,7 @@
|
|||||||
|
|
||||||
#include "tftpc_internal.h"
|
#include "tftpc_internal.h"
|
||||||
|
|
||||||
#if defined(CONFIG_NET) && defined(CONFIG_NET_UDP) && CONFIG_NFILE_DESCRIPTORS > 0
|
#if defined(CONFIG_NET) && defined(CONFIG_NET_UDP)
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Pre-processor Definitions
|
* Pre-processor Definitions
|
||||||
@ -68,12 +70,242 @@
|
|||||||
* Private Functions
|
* Private Functions
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: tftp_parsedatapacket
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static inline int tftp_parsedatapacket(FAR const uint8_t *packet,
|
||||||
|
FAR uint16_t *opcode,
|
||||||
|
FAR uint16_t *blockno)
|
||||||
|
{
|
||||||
|
*opcode = (uint16_t)packet[0] << 8 | (uint16_t)packet[1];
|
||||||
|
if (*opcode == TFTP_DATA)
|
||||||
|
{
|
||||||
|
*blockno = (uint16_t)packet[2] << 8 | (uint16_t)packet[3];
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
#ifdef CONFIG_DEBUG_NET_WARN
|
||||||
|
else if (*opcode == TFTP_ERR)
|
||||||
|
{
|
||||||
|
(void)tftp_parseerrpacket(packet);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Public Functions
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: tftpget_cb
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* remote - The name of the file on the TFTP server.
|
||||||
|
* addr - The IP address of the server in network order
|
||||||
|
* binary - TRUE: Perform binary ('octect') transfer
|
||||||
|
* FALSE: Perform text ('netascii') transfer
|
||||||
|
* cb - callback that will be called with data packets
|
||||||
|
* ctx - pointer passed to the previous callback
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
int tftpget_cb(FAR const char *remote, in_addr_t addr, bool binary,
|
||||||
|
tftp_callback_t tftp_cb, FAR void *ctx)
|
||||||
|
{
|
||||||
|
struct sockaddr_in server; /* The address of the TFTP server */
|
||||||
|
struct sockaddr_in from; /* The address the last UDP message recv'd from */
|
||||||
|
FAR uint8_t *packet; /* Allocated memory to hold one packet */
|
||||||
|
uint16_t blockno = 0; /* The current transfer block number */
|
||||||
|
uint16_t opcode; /* Received opcode */
|
||||||
|
uint16_t rblockno; /* Received block number */
|
||||||
|
int len; /* Generic length */
|
||||||
|
int sd; /* Socket descriptor for socket I/O */
|
||||||
|
int retry; /* Retry counter */
|
||||||
|
int nbytesrecvd = 0; /* The number of bytes received in the packet */
|
||||||
|
int ndatabytes; /* The number of data bytes received */
|
||||||
|
int result = ERROR; /* Assume failure */
|
||||||
|
int ret; /* Generic return status */
|
||||||
|
|
||||||
|
/* Allocate the buffer to used for socket/disk I/O */
|
||||||
|
|
||||||
|
packet = (FAR uint8_t*)zalloc(TFTP_IOBUFSIZE);
|
||||||
|
if (!packet)
|
||||||
|
{
|
||||||
|
nerr("ERROR: packet memory allocation failure\n");
|
||||||
|
set_errno(ENOMEM);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize a UDP socket and setup the server addresss */
|
||||||
|
|
||||||
|
sd = tftp_sockinit(&server, addr);
|
||||||
|
if (sd < 0)
|
||||||
|
{
|
||||||
|
goto errout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then enter the transfer loop. Loop until the entire file has
|
||||||
|
* been received or until an error occurs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
/* Increment the TFTP block number for the next transfer */
|
||||||
|
|
||||||
|
blockno++;
|
||||||
|
|
||||||
|
/* Send the next block if the file within a loop. We will
|
||||||
|
* retry up to TFTP_RETRIES times before giving up on the
|
||||||
|
* transfer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (retry = 0; retry < TFTP_RETRIES; retry++)
|
||||||
|
{
|
||||||
|
/* Send the read request using the well-known port number before
|
||||||
|
* receiving the first block. Each retry of the first block will
|
||||||
|
* re-send the request.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (blockno == 1)
|
||||||
|
{
|
||||||
|
len = tftp_mkreqpacket(packet, TFTP_RRQ, remote,
|
||||||
|
binary);
|
||||||
|
server.sin_port = HTONS(CONFIG_NETUTILS_TFTP_PORT);
|
||||||
|
ret = tftp_sendto(sd, packet, len, &server);
|
||||||
|
if (ret != len)
|
||||||
|
{
|
||||||
|
goto errout_with_sd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subsequent sendto will use the port number selected by the TFTP
|
||||||
|
* server in the DATA packet. Setting the server port to zero
|
||||||
|
* here indicates that we have not yet received the server port
|
||||||
|
* number.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.sin_port = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the next packet from the server */
|
||||||
|
|
||||||
|
nbytesrecvd = tftp_recvfrom(sd, packet, TFTP_IOBUFSIZE, &from);
|
||||||
|
|
||||||
|
/* Check if anything valid was received */
|
||||||
|
|
||||||
|
if (nbytesrecvd > 0)
|
||||||
|
{
|
||||||
|
/* Verify the sender address and port number */
|
||||||
|
|
||||||
|
if (server.sin_addr.s_addr != from.sin_addr.s_addr)
|
||||||
|
{
|
||||||
|
ninfo("Invalid address in DATA\n");
|
||||||
|
retry--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server.sin_port && server.sin_port != from.sin_port)
|
||||||
|
{
|
||||||
|
ninfo("Invalid port in DATA\n");
|
||||||
|
len = tftp_mkerrpacket(packet, TFTP_ERR_UNKID,
|
||||||
|
TFTP_ERRST_UNKID);
|
||||||
|
ret = tftp_sendto(sd, packet, len, &from);
|
||||||
|
retry--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the incoming DATA packet */
|
||||||
|
|
||||||
|
if (nbytesrecvd < TFTP_DATAHEADERSIZE)
|
||||||
|
{
|
||||||
|
/* Packet is not big enough to be parsed */
|
||||||
|
|
||||||
|
ninfo("Tiny data packet ignored\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tftp_parsedatapacket(packet, &opcode, &rblockno) != OK ||
|
||||||
|
blockno != rblockno)
|
||||||
|
{
|
||||||
|
/* Opcode is not TFTP_DATA or the block number is
|
||||||
|
* unexpected.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ninfo("Parse failure\n");
|
||||||
|
if (opcode > TFTP_MAXRFC1350)
|
||||||
|
{
|
||||||
|
len = tftp_mkerrpacket(packet, TFTP_ERR_ILLEGALOP,
|
||||||
|
TFTP_ERRST_ILLEGALOP);
|
||||||
|
ret = tftp_sendto(sd, packet, len, &from);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Replace the server port to the one in the good data response */
|
||||||
|
|
||||||
|
if (!server.sin_port)
|
||||||
|
{
|
||||||
|
server.sin_port = from.sin_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then break out of the loop */
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Did we exhaust all of the retries? */
|
||||||
|
|
||||||
|
if (retry == TFTP_RETRIES)
|
||||||
|
{
|
||||||
|
ninfo("Retry limit exceeded\n");
|
||||||
|
goto errout_with_sd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write the received data chunk to the file */
|
||||||
|
|
||||||
|
ndatabytes = nbytesrecvd - TFTP_DATAHEADERSIZE;
|
||||||
|
tftp_dumpbuffer("Recvd DATA", packet + TFTP_DATAHEADERSIZE, ndatabytes);
|
||||||
|
if (tftp_cb(ctx, 0, packet + TFTP_DATAHEADERSIZE, ndatabytes) < 0)
|
||||||
|
{
|
||||||
|
goto errout_with_sd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send the acknowledgment */
|
||||||
|
|
||||||
|
len = tftp_mkackpacket(packet, blockno);
|
||||||
|
ret = tftp_sendto(sd, packet, len, &server);
|
||||||
|
if (ret != len)
|
||||||
|
{
|
||||||
|
goto errout_with_sd;
|
||||||
|
}
|
||||||
|
ninfo("ACK blockno %d\n", blockno);
|
||||||
|
}
|
||||||
|
while (ndatabytes >= TFTP_DATASIZE);
|
||||||
|
|
||||||
|
/* Return success */
|
||||||
|
|
||||||
|
result = OK;
|
||||||
|
|
||||||
|
errout_with_sd:
|
||||||
|
close(sd);
|
||||||
|
|
||||||
|
errout:
|
||||||
|
free(packet);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CONFIG_NFILE_DESCRIPTORS > 0
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: tftp_write
|
* Name: tftp_write
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
static inline ssize_t tftp_write(int fd, const uint8_t *buf, size_t len)
|
static ssize_t tftp_write(FAR void *ctx, uint32_t offset, FAR uint8_t *buf,
|
||||||
|
size_t len)
|
||||||
{
|
{
|
||||||
|
int fd = (int)ctx;
|
||||||
size_t left = len;
|
size_t left = len;
|
||||||
ssize_t nbyteswritten;
|
ssize_t nbyteswritten;
|
||||||
|
|
||||||
@ -106,33 +338,6 @@ static inline ssize_t tftp_write(int fd, const uint8_t *buf, size_t len)
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Name: tftp_parsedatapacket
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
static inline int tftp_parsedatapacket(const uint8_t *packet,
|
|
||||||
uint16_t *opcode, uint16_t *blockno)
|
|
||||||
{
|
|
||||||
*opcode = (uint16_t)packet[0] << 8 | (uint16_t)packet[1];
|
|
||||||
if (*opcode == TFTP_DATA)
|
|
||||||
{
|
|
||||||
*blockno = (uint16_t)packet[2] << 8 | (uint16_t)packet[3];
|
|
||||||
return OK;
|
|
||||||
}
|
|
||||||
#ifdef CONFIG_DEBUG_NET_WARN
|
|
||||||
else if (*opcode == TFTP_ERR)
|
|
||||||
{
|
|
||||||
(void)tftp_parseerrpacket(packet);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Public Functions
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: tftpget
|
* Name: tftpget
|
||||||
*
|
*
|
||||||
@ -149,30 +354,8 @@ static inline int tftp_parsedatapacket(const uint8_t *packet,
|
|||||||
int tftpget(FAR const char *remote, FAR const char *local, in_addr_t addr,
|
int tftpget(FAR const char *remote, FAR const char *local, in_addr_t addr,
|
||||||
bool binary)
|
bool binary)
|
||||||
{
|
{
|
||||||
struct sockaddr_in server; /* The address of the TFTP server */
|
|
||||||
struct sockaddr_in from; /* The address the last UDP message recv'd from */
|
|
||||||
FAR uint8_t *packet; /* Allocated memory to hold one packet */
|
|
||||||
uint16_t blockno = 0; /* The current transfer block number */
|
|
||||||
uint16_t opcode; /* Received opcode */
|
|
||||||
uint16_t rblockno; /* Received block number */
|
|
||||||
int len; /* Generic length */
|
|
||||||
int sd; /* Socket descriptor for socket I/O */
|
|
||||||
int fd; /* File descriptor for file I/O */
|
int fd; /* File descriptor for file I/O */
|
||||||
int retry; /* Retry counter */
|
int result = ERROR; /* Generic return status */
|
||||||
int nbytesrecvd = 0; /* The number of bytes received in the packet */
|
|
||||||
int ndatabytes; /* The number of data bytes received */
|
|
||||||
int result = ERROR; /* Assume failure */
|
|
||||||
int ret; /* Generic return status */
|
|
||||||
|
|
||||||
/* Allocate the buffer to used for socket/disk I/O */
|
|
||||||
|
|
||||||
packet = (FAR uint8_t*)zalloc(TFTP_IOBUFSIZE);
|
|
||||||
if (!packet)
|
|
||||||
{
|
|
||||||
nerr("ERROR: packet memory allocation failure\n");
|
|
||||||
set_errno(ENOMEM);
|
|
||||||
goto errout;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Open the file for writing */
|
/* Open the file for writing */
|
||||||
|
|
||||||
@ -180,161 +363,16 @@ int tftpget(FAR const char *remote, FAR const char *local, in_addr_t addr,
|
|||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
{
|
{
|
||||||
nerr("ERROR: open failed: %d\n", errno);
|
nerr("ERROR: open failed: %d\n", errno);
|
||||||
goto errout_with_packet;
|
goto errout;
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize a UDP socket and setup the server addresss */
|
|
||||||
|
|
||||||
sd = tftp_sockinit(&server, addr);
|
|
||||||
if (sd < 0)
|
|
||||||
{
|
|
||||||
goto errout_with_fd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Then enter the transfer loop. Loop until the entire file has
|
result = tftpget_cb(remote, addr, binary, tftp_write, (void*)fd);
|
||||||
* been received or until an error occurs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
/* Increment the TFTP block number for the next transfer */
|
|
||||||
|
|
||||||
blockno++;
|
|
||||||
|
|
||||||
/* Send the next block if the file within a loop. We will
|
|
||||||
* retry up to TFTP_RETRIES times before giving up on the
|
|
||||||
* transfer.
|
|
||||||
*/
|
|
||||||
|
|
||||||
for (retry = 0; retry < TFTP_RETRIES; retry++)
|
|
||||||
{
|
|
||||||
/* Send the read request using the well-known port number before
|
|
||||||
* receiving the first block. Each retry of the first block will
|
|
||||||
* re-send the request.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (blockno == 1)
|
|
||||||
{
|
|
||||||
len = tftp_mkreqpacket(packet, TFTP_RRQ, remote, binary);
|
|
||||||
server.sin_port = HTONS(CONFIG_NETUTILS_TFTP_PORT);
|
|
||||||
ret = tftp_sendto(sd, packet, len, &server);
|
|
||||||
if (ret != len)
|
|
||||||
{
|
|
||||||
goto errout_with_sd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Subsequent sendto will use the port number selected by the TFTP
|
|
||||||
* server in the DATA packet. Setting the server port to zero
|
|
||||||
* here indicates that we have not yet received the server port number.
|
|
||||||
*/
|
|
||||||
|
|
||||||
server.sin_port = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get the next packet from the server */
|
|
||||||
|
|
||||||
nbytesrecvd = tftp_recvfrom(sd, packet, TFTP_IOBUFSIZE, &from);
|
|
||||||
|
|
||||||
/* Check if anything valid was received */
|
|
||||||
|
|
||||||
if (nbytesrecvd > 0)
|
|
||||||
{
|
|
||||||
/* Verify the sender address and port number */
|
|
||||||
|
|
||||||
if (server.sin_addr.s_addr != from.sin_addr.s_addr)
|
|
||||||
{
|
|
||||||
ninfo("Invalid address in DATA\n");
|
|
||||||
retry--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (server.sin_port && server.sin_port != from.sin_port)
|
|
||||||
{
|
|
||||||
ninfo("Invalid port in DATA\n");
|
|
||||||
len = tftp_mkerrpacket(packet, TFTP_ERR_UNKID, TFTP_ERRST_UNKID);
|
|
||||||
ret = tftp_sendto(sd, packet, len, &from);
|
|
||||||
retry--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse the incoming DATA packet */
|
|
||||||
|
|
||||||
if (nbytesrecvd < TFTP_DATAHEADERSIZE)
|
|
||||||
{
|
|
||||||
/* Packet is not big enough to be parsed */
|
|
||||||
|
|
||||||
ninfo("Tiny data packet ignored\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tftp_parsedatapacket(packet, &opcode, &rblockno) != OK ||
|
|
||||||
blockno != rblockno)
|
|
||||||
{
|
|
||||||
/* Opcode is not TFTP_DATA or the block number is unexpected */
|
|
||||||
|
|
||||||
ninfo("Parse failure\n");
|
|
||||||
if (opcode > TFTP_MAXRFC1350)
|
|
||||||
{
|
|
||||||
len = tftp_mkerrpacket(packet, TFTP_ERR_ILLEGALOP, TFTP_ERRST_ILLEGALOP);
|
|
||||||
ret = tftp_sendto(sd, packet, len, &from);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Replace the server port to the one in the good data response */
|
|
||||||
|
|
||||||
if (!server.sin_port)
|
|
||||||
{
|
|
||||||
server.sin_port = from.sin_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Then break out of the loop */
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Did we exhaust all of the retries? */
|
|
||||||
|
|
||||||
if (retry == TFTP_RETRIES)
|
|
||||||
{
|
|
||||||
ninfo("Retry limit exceeded\n");
|
|
||||||
goto errout_with_sd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write the received data chunk to the file */
|
|
||||||
|
|
||||||
ndatabytes = nbytesrecvd - TFTP_DATAHEADERSIZE;
|
|
||||||
tftp_dumpbuffer("Recvd DATA", packet + TFTP_DATAHEADERSIZE, ndatabytes);
|
|
||||||
if (tftp_write(fd, packet + TFTP_DATAHEADERSIZE, ndatabytes) < 0)
|
|
||||||
{
|
|
||||||
goto errout_with_sd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Send the acknowledgment */
|
|
||||||
|
|
||||||
len = tftp_mkackpacket(packet, blockno);
|
|
||||||
ret = tftp_sendto(sd, packet, len, &server);
|
|
||||||
if (ret != len)
|
|
||||||
{
|
|
||||||
goto errout_with_sd;
|
|
||||||
}
|
|
||||||
ninfo("ACK blockno %d\n", blockno);
|
|
||||||
}
|
|
||||||
while (ndatabytes >= TFTP_DATASIZE);
|
|
||||||
|
|
||||||
/* Return success */
|
|
||||||
|
|
||||||
result = OK;
|
|
||||||
|
|
||||||
errout_with_sd:
|
|
||||||
close(sd);
|
|
||||||
errout_with_fd:
|
|
||||||
close(fd);
|
close(fd);
|
||||||
errout_with_packet:
|
|
||||||
free(packet);
|
|
||||||
errout:
|
errout:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* CONFIG_NET && CONFIG_NET_UDP && CONFIG_NFILE_DESCRIPTORS > 0 */
|
#endif /* CONFIG_NET && CONFIG_NET_UDP && CONFIG_NFILE_DESCRIPTORS > 0 */
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* netuils/tftp/tftpc_put.c
|
* netuils/tftp/tftpc_put.c
|
||||||
*
|
*
|
||||||
* Copyright (C) 2008-2009, 2011 Gregory Nutt. All rights reserved.
|
* Copyright (C) 2008-2009, 2011, 2018 Gregory Nutt. All rights reserved.
|
||||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||||
|
* Copyright (C) 2018 Sebastien Lorquet. All rights reserved.
|
||||||
|
* Author: Sebastien Lorquet <sebastien@lorquet.fr>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@ -54,7 +56,7 @@
|
|||||||
|
|
||||||
#include "tftpc_internal.h"
|
#include "tftpc_internal.h"
|
||||||
|
|
||||||
#if defined(CONFIG_NET) && defined(CONFIG_NET_UDP) && CONFIG_NFILE_DESCRIPTORS > 0
|
#if defined(CONFIG_NET) && defined(CONFIG_NET_UDP)
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Pre-processor Definitions
|
* Pre-processor Definitions
|
||||||
@ -70,53 +72,6 @@
|
|||||||
* Private Functions
|
* Private Functions
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Name: tftp_read
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
static inline ssize_t tftp_read(int fd, uint8_t *buf, size_t buflen)
|
|
||||||
{
|
|
||||||
ssize_t nbytesread;
|
|
||||||
ssize_t totalread = 0;
|
|
||||||
|
|
||||||
while (totalread < buflen)
|
|
||||||
{
|
|
||||||
/* Read the data... repeating the read in the event that it was
|
|
||||||
* interrupted by a signal.
|
|
||||||
*/
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
nbytesread = read(fd, buf, buflen - totalread);
|
|
||||||
}
|
|
||||||
while (nbytesread < 0 && errno == EINTR);
|
|
||||||
|
|
||||||
/* Check for non-EINTR errors */
|
|
||||||
|
|
||||||
if (nbytesread < 0)
|
|
||||||
{
|
|
||||||
nerr("ERROR: read failed: %d\n", errno);
|
|
||||||
return ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for end of file */
|
|
||||||
|
|
||||||
else if (nbytesread == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handle partial reads. Partial reads can happen normally
|
|
||||||
* when the source is some device driver that returns data
|
|
||||||
* in bits and pieces as received (such as a pipe)
|
|
||||||
*/
|
|
||||||
|
|
||||||
totalread += nbytesread;
|
|
||||||
buf += nbytesread;
|
|
||||||
}
|
|
||||||
return totalread;
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: tftp_mkdatapacket
|
* Name: tftp_mkdatapacket
|
||||||
*
|
*
|
||||||
@ -139,9 +94,9 @@ static inline ssize_t tftp_read(int fd, uint8_t *buf, size_t buflen)
|
|||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
int tftp_mkdatapacket(int fd, off_t offset, uint8_t *packet, uint16_t blockno)
|
int tftp_mkdatapacket(off_t offset, FAR uint8_t *packet, uint16_t blockno,
|
||||||
|
tftp_callback_t tftp_cb, FAR void *ctx)
|
||||||
{
|
{
|
||||||
off_t tmp;
|
|
||||||
int nbytesread;
|
int nbytesread;
|
||||||
|
|
||||||
/* Format the DATA message header */
|
/* Format the DATA message header */
|
||||||
@ -151,18 +106,8 @@ int tftp_mkdatapacket(int fd, off_t offset, uint8_t *packet, uint16_t blockno)
|
|||||||
packet[2] = blockno >> 8;
|
packet[2] = blockno >> 8;
|
||||||
packet[3] = blockno & 0xff;
|
packet[3] = blockno & 0xff;
|
||||||
|
|
||||||
/* Seek to the correct offset in the file */
|
nbytesread = tftp_cb(ctx, offset, &packet[TFTP_DATAHEADERSIZE],
|
||||||
|
TFTP_DATASIZE);
|
||||||
tmp = lseek(fd, offset, SEEK_SET);
|
|
||||||
if (tmp == (off_t)-1)
|
|
||||||
{
|
|
||||||
nerr("ERROR: lseek failed: %d\n", errno);
|
|
||||||
return ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read the file data into the packet buffer */
|
|
||||||
|
|
||||||
nbytesread = tftp_read(fd, &packet[TFTP_DATAHEADERSIZE], TFTP_DATASIZE);
|
|
||||||
if (nbytesread < 0)
|
if (nbytesread < 0)
|
||||||
{
|
{
|
||||||
return ERROR;
|
return ERROR;
|
||||||
@ -191,10 +136,11 @@ int tftp_mkdatapacket(int fd, off_t offset, uint8_t *packet, uint16_t blockno)
|
|||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
static int tftp_rcvack(int sd, uint8_t *packet, struct sockaddr_in *server,
|
static int tftp_rcvack(int sd, FAR uint8_t *packet,
|
||||||
uint16_t *port, uint16_t *blockno)
|
FAR struct sockaddr_in *server, FAR uint16_t *port,
|
||||||
|
FAR uint16_t *blockno)
|
||||||
{
|
{
|
||||||
struct sockaddr_in from; /* The address the last UDP message recv'd from */
|
struct sockaddr_in from; /* The address the last UDP msg recv'd from */
|
||||||
ssize_t nbytes; /* The number of bytes received. */
|
ssize_t nbytes; /* The number of bytes received. */
|
||||||
uint16_t opcode; /* The received opcode */
|
uint16_t opcode; /* The received opcode */
|
||||||
uint16_t rblockno; /* The received block number */
|
uint16_t rblockno; /* The received block number */
|
||||||
@ -235,7 +181,9 @@ static int tftp_rcvack(int sd, uint8_t *packet, struct sockaddr_in *server,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Get the port being used by the server if that has not yet been established */
|
/* Get the port being used by the server if that has not yet
|
||||||
|
* been established.
|
||||||
|
*/
|
||||||
|
|
||||||
if (!*port)
|
if (!*port)
|
||||||
{
|
{
|
||||||
@ -243,7 +191,9 @@ static int tftp_rcvack(int sd, uint8_t *packet, struct sockaddr_in *server,
|
|||||||
server->sin_port = from.sin_port;
|
server->sin_port = from.sin_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Verify that the packet was received from the correct host and port */
|
/* Verify that the packet was received from the correct host and
|
||||||
|
* port.
|
||||||
|
*/
|
||||||
|
|
||||||
if (server->sin_addr.s_addr != from.sin_addr.s_addr)
|
if (server->sin_addr.s_addr != from.sin_addr.s_addr)
|
||||||
{
|
{
|
||||||
@ -254,7 +204,8 @@ static int tftp_rcvack(int sd, uint8_t *packet, struct sockaddr_in *server,
|
|||||||
if (*port != server->sin_port)
|
if (*port != server->sin_port)
|
||||||
{
|
{
|
||||||
ninfo("Invalid port in DATA\n");
|
ninfo("Invalid port in DATA\n");
|
||||||
packetlen = tftp_mkerrpacket(packet, TFTP_ERR_UNKID, TFTP_ERRST_UNKID);
|
packetlen = tftp_mkerrpacket(packet, TFTP_ERR_UNKID,
|
||||||
|
TFTP_ERRST_UNKID);
|
||||||
(void)tftp_sendto(sd, packet, packetlen, server);
|
(void)tftp_sendto(sd, packet, packetlen, server);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -281,7 +232,8 @@ static int tftp_rcvack(int sd, uint8_t *packet, struct sockaddr_in *server,
|
|||||||
#endif
|
#endif
|
||||||
if (opcode > TFTP_MAXRFC1350)
|
if (opcode > TFTP_MAXRFC1350)
|
||||||
{
|
{
|
||||||
packetlen = tftp_mkerrpacket(packet, TFTP_ERR_ILLEGALOP, TFTP_ERRST_ILLEGALOP);
|
packetlen = tftp_mkerrpacket(packet, TFTP_ERR_ILLEGALOP,
|
||||||
|
TFTP_ERRST_ILLEGALOP);
|
||||||
(void)tftp_sendto(sd, packet, packetlen, server);
|
(void)tftp_sendto(sd, packet, packetlen, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,35 +262,36 @@ static int tftp_rcvack(int sd, uint8_t *packet, struct sockaddr_in *server,
|
|||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: tftpput
|
* Name: tftpput_cb
|
||||||
*
|
*
|
||||||
* Input Parameters:
|
* Input Parameters:
|
||||||
* local - Path to the file system object to be sent.
|
|
||||||
* remote - The name of the file on the TFTP server.
|
* remote - The name of the file on the TFTP server.
|
||||||
* addr - The IP address of the server in network order
|
* addr - The IP address of the server in network order
|
||||||
* binary - TRUE: Perform binary ('octect') transfer
|
* binary - TRUE: Perform binary ('octect') transfer
|
||||||
* FALSE: Perform text ('netascii') transfer
|
* FALSE: Perform text ('netascii') transfer
|
||||||
|
* cb - callback that will be called with data packets
|
||||||
|
* ctx - pointer passed to the previous callback
|
||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
int tftpput(const char *local, const char *remote, in_addr_t addr, bool binary)
|
int tftpput_cb(FAR const char *remote, in_addr_t addr, bool binary,
|
||||||
|
tftp_callback_t cb, FAR void *ctx)
|
||||||
{
|
{
|
||||||
struct sockaddr_in server; /* The address of the TFTP server */
|
struct sockaddr_in server; /* The address of the TFTP server */
|
||||||
uint8_t *packet; /* Allocated memory to hold one packet */
|
FAR uint8_t *packet; /* Allocated memory to hold one packet */
|
||||||
off_t offset; /* Offset into source file */
|
off_t offset; /* Offset into source file */
|
||||||
uint16_t blockno; /* The current transfer block number */
|
uint16_t blockno; /* The current transfer block number */
|
||||||
uint16_t rblockno; /* The ACK'ed block number */
|
uint16_t rblockno; /* The ACK'ed block number */
|
||||||
uint16_t port = 0; /* This is the port number for the transfer */
|
uint16_t port = 0; /* This is the port nbr for the transfer */
|
||||||
int packetlen; /* The length of the data packet */
|
int packetlen; /* The length of the data packet */
|
||||||
int sd; /* Socket descriptor for socket I/O */
|
int sd; /* Socket descriptor for socket I/O */
|
||||||
int fd; /* File descriptor for file I/O */
|
|
||||||
int retry; /* Retry counter */
|
int retry; /* Retry counter */
|
||||||
int result = ERROR; /* Assume failure */
|
int result = ERROR; /* Assume failure */
|
||||||
int ret; /* Generic return status */
|
int ret; /* Generic return status */
|
||||||
|
|
||||||
/* Allocate the buffer to used for socket/disk I/O */
|
/* Allocate the buffer to used for socket/disk I/O */
|
||||||
|
|
||||||
packet = (uint8_t*)zalloc(TFTP_IOBUFSIZE);
|
packet = (FAR uint8_t*)zalloc(TFTP_IOBUFSIZE);
|
||||||
if (!packet)
|
if (!packet)
|
||||||
{
|
{
|
||||||
nerr("ERROR: packet memory allocation failure\n");
|
nerr("ERROR: packet memory allocation failure\n");
|
||||||
@ -346,21 +299,12 @@ int tftpput(const char *local, const char *remote, in_addr_t addr, bool binary)
|
|||||||
goto errout;
|
goto errout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Open the file for reading */
|
|
||||||
|
|
||||||
fd = open(local, O_RDONLY);
|
|
||||||
if (fd < 0)
|
|
||||||
{
|
|
||||||
nerr("ERROR: open failed: %d\n", errno);
|
|
||||||
goto errout_with_packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize a UDP socket and setup the server addresss */
|
/* Initialize a UDP socket and setup the server addresss */
|
||||||
|
|
||||||
sd = tftp_sockinit(&server, addr);
|
sd = tftp_sockinit(&server, addr);
|
||||||
if (sd < 0)
|
if (sd < 0)
|
||||||
{
|
{
|
||||||
goto errout_with_fd;
|
goto errout_with_packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send the write request using the well known port. This may need
|
/* Send the write request using the well known port. This may need
|
||||||
@ -410,7 +354,7 @@ int tftpput(const char *local, const char *remote, in_addr_t addr, bool binary)
|
|||||||
{
|
{
|
||||||
/* Construct the next data packet */
|
/* Construct the next data packet */
|
||||||
|
|
||||||
packetlen = tftp_mkdatapacket(fd, offset, packet, blockno);
|
packetlen = tftp_mkdatapacket(offset, packet, blockno, cb, ctx);
|
||||||
if (packetlen < 0)
|
if (packetlen < 0)
|
||||||
{
|
{
|
||||||
goto errout_with_sd;
|
goto errout_with_sd;
|
||||||
@ -435,8 +379,8 @@ int tftpput(const char *local, const char *remote, in_addr_t addr, bool binary)
|
|||||||
|
|
||||||
if (rblockno == blockno)
|
if (rblockno == blockno)
|
||||||
{
|
{
|
||||||
/* Yes.. If we are at the end of the file and if all of the packets
|
/* Yes.. If we are at the end of the file and if all of the
|
||||||
* have been ACKed, then we are done.
|
* packets have been ACKed, then we are done.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (packetlen < TFTP_PACKETSIZE)
|
if (packetlen < TFTP_PACKETSIZE)
|
||||||
@ -446,9 +390,9 @@ int tftpput(const char *local, const char *remote, in_addr_t addr, bool binary)
|
|||||||
|
|
||||||
/* Not the last block.. set up for the next block */
|
/* Not the last block.. set up for the next block */
|
||||||
|
|
||||||
blockno++;
|
blockno += 1;
|
||||||
offset += TFTP_DATASIZE;
|
offset += TFTP_DATASIZE;
|
||||||
retry = 0;
|
retry = 0;
|
||||||
|
|
||||||
/* Skip the retry test */
|
/* Skip the retry test */
|
||||||
|
|
||||||
@ -474,12 +418,108 @@ int tftpput(const char *local, const char *remote, in_addr_t addr, bool binary)
|
|||||||
|
|
||||||
errout_with_sd:
|
errout_with_sd:
|
||||||
close(sd);
|
close(sd);
|
||||||
errout_with_fd:
|
|
||||||
close(fd);
|
|
||||||
errout_with_packet:
|
errout_with_packet:
|
||||||
free(packet);
|
free(packet);
|
||||||
errout:
|
errout:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CONFIG_NFILE_DESCRIPTORS > 0
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: tftp_read
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static ssize_t tftp_read(FAR void *ctx, uint32_t offset, FAR uint8_t *buf,
|
||||||
|
size_t buflen)
|
||||||
|
{
|
||||||
|
int fd = (int)ctx;
|
||||||
|
off_t tmp;
|
||||||
|
ssize_t nbytesread;
|
||||||
|
ssize_t totalread = 0;
|
||||||
|
|
||||||
|
/* Seek to the correct offset in the file */
|
||||||
|
|
||||||
|
tmp = lseek(fd, offset, SEEK_SET);
|
||||||
|
if (tmp == (off_t)-1)
|
||||||
|
{
|
||||||
|
nerr("ERROR: lseek failed: %d\n", errno);
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read the file data into the packet buffer */
|
||||||
|
|
||||||
|
while (totalread < buflen)
|
||||||
|
{
|
||||||
|
/* Read the data... repeating the read in the event that it was
|
||||||
|
* interrupted by a signal.
|
||||||
|
*/
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
nbytesread = read(fd, buf, buflen - totalread);
|
||||||
|
}
|
||||||
|
while (nbytesread < 0 && errno == EINTR);
|
||||||
|
|
||||||
|
/* Check for non-EINTR errors */
|
||||||
|
|
||||||
|
if (nbytesread < 0)
|
||||||
|
{
|
||||||
|
nerr("ERROR: read failed: %d\n", errno);
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for end of file */
|
||||||
|
|
||||||
|
else if (nbytesread == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle partial reads. Partial reads can happen normally
|
||||||
|
* when the source is some device driver that returns data
|
||||||
|
* in bits and pieces as received (such as a pipe)
|
||||||
|
*/
|
||||||
|
|
||||||
|
totalread += nbytesread;
|
||||||
|
buf += nbytesread;
|
||||||
|
}
|
||||||
|
return totalread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: tftpput
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* local - Path to the file system object to be sent.
|
||||||
|
* remote - The name of the file on the TFTP server.
|
||||||
|
* addr - The IP address of the server in network order
|
||||||
|
* binary - TRUE: Perform binary ('octect') transfer
|
||||||
|
* FALSE: Perform text ('netascii') transfer
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
int tftpput(FAR const char *local, FAR const char *remote, in_addr_t addr,
|
||||||
|
bool binary)
|
||||||
|
{
|
||||||
|
int fd; /* File descriptor for file I/O */
|
||||||
|
int result = ERROR; /* Assume failure */
|
||||||
|
|
||||||
|
/* Open the file for reading */
|
||||||
|
|
||||||
|
fd = open(local, O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
nerr("ERROR: open failed: %d\n", errno);
|
||||||
|
goto errout;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = tftpput_cb(remote, addr, binary, tftp_read, (void *)fd);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
errout:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* CONFIG_NET && CONFIG_NET_UDP && CONFIG_NFILE_DESCRIPTORS > 0 */
|
#endif /* CONFIG_NET && CONFIG_NET_UDP && CONFIG_NFILE_DESCRIPTORS > 0 */
|
||||||
|
Loading…
Reference in New Issue
Block a user