[PATCH] net: lwip: tftp: add support of tsize option to client

Jerome Forissier jerome.forissier at arm.com
Thu Jan 29 11:04:16 CET 2026


Hi Marek,

On 29/01/2026 00:43, Marek Vasut wrote:
> The TFTP server can report the size of the entire file that is about to
> be received in the Transfer Size Option, this is described in RFC 2349.
> This functionality is optional and the server may not report tsize in
> case it is not supported.
> 
> Always send tsize request to the server to query the transfer size,
> and in case the server does respond, cache that information locally
> in tftp_state.tsize, otherwise cache size 0. Introduce new function
> tftp_client_get_tsize() which returns the cached tftp_state.tsize so
> clients can determine the transfer size and use it.
> 
> Update net/lwip/tftp.c to make use of tftp_client_get_tsize() and
> avoid excessive printing of '#' during TFTP transfers in case the
> transfer size is reported by the server.
> 
> Submitted upstream: https://savannah.nongnu.org/patch/index.php?item_id=10557
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas at mailbox.org>

Nice feature! One minor comment below.

Acked-by: Jerome Forissier <jerome.forissier at arm.com>

> ---
> Cc: Andrew Goodbody <andrew.goodbody at linaro.org>
> Cc: Heinrich Schuchardt <xypron.glpk at gmx.de>
> Cc: Ilias Apalodimas <ilias.apalodimas at linaro.org>
> Cc: Jerome Forissier <jerome at forissier.org>
> Cc: Joe Hershberger <joe.hershberger at ni.com>
> Cc: Ramon Fried <rfried.dev at gmail.com>
> Cc: Tim Harvey <tharvey at gateworks.com>
> Cc: Tom Rini <trini at konsulko.com>
> Cc: Wolfgang Wallner <wolfgang.wallner at at.abb.com>
> Cc: u-boot at lists.denx.de
> ---
>  lib/lwip/lwip/src/apps/tftp/tftp.c            | 51 +++++++++++++++----
>  .../lwip/src/include/lwip/apps/tftp_client.h  |  1 +
>  net/lwip/tftp.c                               | 35 +++++++++++--
>  3 files changed, 74 insertions(+), 13 deletions(-)
> 
> diff --git a/lib/lwip/lwip/src/apps/tftp/tftp.c b/lib/lwip/lwip/src/apps/tftp/tftp.c
> index ecb6c55ae11..5061446b1cf 100644
> --- a/lib/lwip/lwip/src/apps/tftp/tftp.c
> +++ b/lib/lwip/lwip/src/apps/tftp/tftp.c
> @@ -98,6 +98,7 @@ struct tftp_state {
>    int last_pkt;
>    u16_t blknum;
>    u16_t blksize;
> +  u32_t tsize;
>    u8_t retries;
>    u8_t mode_write;
>    u8_t tftp_mode;
> @@ -167,6 +168,7 @@ send_request(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* fname,
>  {
>    size_t fname_length = strlen(fname)+1;
>    size_t mode_length = strlen(mode)+1;
> +  size_t tsize_length = strlen("tsize")+3; /* "tsize\0\0\0" */
>    size_t blksize_length = 0;
>    int blksize = tftp_state.blksize;
>    struct pbuf* p;
> @@ -182,7 +184,7 @@ send_request(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* fname,
>      }
>    }
>  
> -  p = init_packet(opcode, 0, fname_length + mode_length + blksize_length - 2);
> +  p = init_packet(opcode, 0, fname_length + mode_length + blksize_length + tsize_length - 2);
>    if (p == NULL) {
>      return ERR_MEM;
>    }
> @@ -190,8 +192,9 @@ send_request(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* fname,
>    payload = (char*) p->payload;
>    MEMCPY(payload+2,              fname, fname_length);
>    MEMCPY(payload+2+fname_length, mode,  mode_length);
> +  sprintf(payload+2+fname_length+mode_length, "tsize%c%u%c", 0, 0, 0);
>    if (tftp_state.blksize)
> -    sprintf(payload+2+fname_length+mode_length, "blksize%c%d%c", 0, tftp_state.blksize, 0);
> +    sprintf(payload+2+fname_length+mode_length+tsize_length, "blksize%c%d%c", 0, tftp_state.blksize, 0);
>  
>    tftp_state.wait_oack = true;
>    ret = udp_sendto(tftp_state.upcb, p, addr, port);
> @@ -450,14 +453,24 @@ tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr
>        }
>  
>        blknum = lwip_ntohs(sbuf[1]);
> -      if (tftp_state.blksize && tftp_state.wait_oack) {
> +      if (tftp_state.wait_oack) {
>          /*
> -         * Data received while we are expecting an OACK for our blksize option.
> +         * Data received while we are expecting an OACK for our tsize option.
>           * This means the server doesn't support it, let's switch back to the
>           * default block size.
>           */
> -       tftp_state.blksize = 0;
> -       tftp_state.wait_oack = false;
> +        tftp_state.tsize = 0;
> +        tftp_state.wait_oack = false;
> +
> +        if (tftp_state.blksize) {
> +          /*
> +           * Data received while we are expecting an OACK for our blksize option.
> +           * This means the server doesn't support it, let's switch back to the
> +           * default block size.
> +           */
> +          tftp_state.blksize = 0;
> +          tftp_state.wait_oack = false;
> +        }
>        }
>        if (blknum == tftp_state.blknum) {
>          pbuf_remove_header(p, TFTP_HEADER_LENGTH);
> @@ -527,21 +540,31 @@ tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr
>        }
>        break;
>      case PP_HTONS(TFTP_OACK): {
> -      const char *optval = find_option(p, "blksize");
> +      const char *blksizeoptval = find_option(p, "blksize");
> +      const char *tsizeoptval = find_option(p, "tsize");
>        u16_t srv_blksize = 0;
> +      u32_t srv_tsize = 0;
>        tftp_state.wait_oack = false;
> -      if (optval) {
> +      if (blksizeoptval) {
>  	if (!tftp_state.blksize) {
>  	  /* We did not request this option */
>            send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "blksize unexpected");
>  	}
> -	srv_blksize = atoi(optval);
> +	srv_blksize = atoi(blksizeoptval);
>  	if (srv_blksize <= 0 || srv_blksize > tftp_state.blksize) {
>  	  send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "Invalid blksize");
>  	}
>  	LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: accepting blksize=%d\n", srv_blksize));
>  	tftp_state.blksize = srv_blksize;
>        }
> +      if (tsizeoptval) {
> +	srv_tsize = atoi(tsizeoptval);
> +	if (srv_tsize <= 0) {
> +	  srv_tsize = 0; /* tsize is optional */
> +	}
> +	LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: accepting tsize=%d\n", srv_tsize));
> +	tftp_state.tsize = srv_tsize;
> +      }
>        send_ack(addr, port, 0);
>        break;
>      }
> @@ -655,6 +678,16 @@ tftp_init_client(const struct tftp_context *ctx)
>    return tftp_init_common(LWIP_TFTP_MODE_CLIENT, ctx);
>  }
>  
> +/** @ingroup tftp
> + * Get the transfer size used by the TFTP client. The server may
> + * report zero in case this is unsupported.
> + */
> +u32_t
> +tftp_client_get_tsize(void)
> +{
> +  return tftp_state.tsize;
> +}
> +
>  /** @ingroup tftp
>   * Set the block size to be used by the TFTP client. The server may choose to
>   * accept a lower value.
> diff --git a/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h b/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h
> index e1e21d06b67..78de50f4924 100644
> --- a/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h
> +++ b/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h
> @@ -45,6 +45,7 @@ enum tftp_transfer_mode {
>  
>  err_t tftp_init_client(const struct tftp_context* ctx);
>  void tftp_client_set_blksize(u16_t blksize);
> +u32_t tftp_client_get_tsize(void);
>  err_t tftp_get(void* handle, const ip_addr_t *addr, u16_t port, const char* fname, enum tftp_transfer_mode mode);
>  err_t tftp_put(void* handle, const ip_addr_t *addr, u16_t port, const char* fname, enum tftp_transfer_mode mode);
>  
> diff --git a/net/lwip/tftp.c b/net/lwip/tftp.c
> index 6c7ffba661e..213c1292eb1 100644
> --- a/net/lwip/tftp.c
> +++ b/net/lwip/tftp.c
> @@ -31,6 +31,7 @@ struct tftp_ctx {
>  	ulong daddr;
>  	ulong size;
>  	ulong block_count;
> +	ulong hash_count;
>  	ulong start_time;
>  	enum done_state done;
>  };
> @@ -49,6 +50,8 @@ struct tftp_ctx {
>  static int store_block(struct tftp_ctx *ctx, void *src, u16_t len)
>  {
>  	ulong store_addr = ctx->daddr;
> +	ulong tftp_tsize;
> +	ulong pos;
>  	void *ptr;
>  
>  	if (CONFIG_IS_ENABLED(LMB)) {
> @@ -67,10 +70,21 @@ static int store_block(struct tftp_ctx *ctx, void *src, u16_t len)
>  	ctx->daddr += len;
>  	ctx->size += len;
>  	ctx->block_count++;
> -	if (ctx->block_count % 10 == 0) {
> -		putc('#');
> -		if (ctx->block_count % (65 * 10) == 0)
> -			puts("\n\t ");
> +
> +	tftp_tsize = tftp_client_get_tsize();
> +	if (tftp_tsize) {
> +		pos = clamp(ctx->size, 0UL, tftp_tsize);
> +
> +		while (ctx->hash_count < pos * 50 / tftp_tsize) {
> +			putc('#');
> +			ctx->hash_count++;
> +		}

Nit: with this format being the same as the non-tsize one, the user has no
way to know how many hashes to expect. How about printing percentages instead?

 0%....10%....20%.... etc.

(functional tests may need adjusting though)

> +	} else {
> +		if (ctx->block_count % 10 == 0) {
> +			putc('#');
> +			if (ctx->block_count % (65 * 10) == 0)
> +				puts("\n\t ");
> +		}
>  	}
>  
>  	return 0;
> @@ -84,6 +98,7 @@ static void *tftp_open(const char *fname, const char *mode, u8_t is_write)
>  static void tftp_close(void *handle)
>  {
>  	struct tftp_ctx *ctx = handle;
> +	ulong tftp_tsize;
>  	ulong elapsed;
>  
>  	if (ctx->done == FAILURE || ctx->done == ABORTED) {
> @@ -92,6 +107,17 @@ static void tftp_close(void *handle)
>  	}
>  	ctx->done = SUCCESS;
>  
> +	tftp_tsize = tftp_client_get_tsize();
> +	if (tftp_tsize) {
> +		/* Print hash marks for the last packet received */
> +		while (ctx->hash_count < 49) {
> +			putc('#');
> +			ctx->hash_count++;
> +		}
> +		puts("  ");
> +		print_size(tftp_tsize, "");
> +	}
> +
>  	elapsed = get_timer(ctx->start_time);
>  	if (elapsed > 0) {
>  		puts("\n\t ");	/* Line up with "Loading: " */
> @@ -176,6 +202,7 @@ static int tftp_loop(struct udevice *udev, ulong addr, char *fname,
>  	ctx.done = NOT_DONE;
>  	ctx.size = 0;
>  	ctx.block_count = 0;
> +	ctx.hash_count = 0;
>  	ctx.daddr = addr;
>  
>  	printf("Using %s device\n", udev->name);



More information about the U-Boot mailing list