/**************************************************************************** * apps/netutils/tftpc/tftpc_put.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "netutils/tftp.h" #include "tftpc_internal.h" #if defined(CONFIG_NET) && defined(CONFIG_NET_UDP) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define TFTP_RETRIES 3 /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: tftp_mkdatapacket * * Description: * DATA message format: * * 2 bytes: Opcode (network order == big-endian) * 2 bytes: Block number (network order == big-endian) * N bytes: Data (where N <= 512) * * Input Parameters: * fd - File descriptor used to read from the file * offset - File offset to read from * packet - Buffer to write the data packet into * blockno - The block number of the packet * * Return Value: * Number of bytes read into the packet. > 8; packet[1] = TFTP_DATA & 0xff; packet[2] = blockno >> 8; packet[3] = blockno & 0xff; nbytesread = tftp_cb(ctx, offset, &packet[TFTP_DATAHEADERSIZE], TFTP_DATASIZE); if (nbytesread < 0) { return ERROR; } return nbytesread + TFTP_DATAHEADERSIZE; } /**************************************************************************** * Name: tftp_rcvack * * Description: * ACK message format: * * 2 bytes: Opcode (network order == big-endian) * 2 bytes: Block number (network order == big-endian) * * Input Parameters: * sd - Socket descriptor to use in in the transfer * packet - buffer to use for the transfers * server - The address of the server * port - The port number of the server (0 if not yet known) * blockno - Location to return block number in the received ACK * * Returned Value: * OK:success and blockno valid, ERROR:failure. * ****************************************************************************/ static int tftp_rcvack(int sd, FAR uint8_t *packet, FAR struct sockaddr_in *server, FAR uint16_t *port, FAR uint16_t *blockno) { struct sockaddr_in from; /* The address the last UDP msg recv'd from */ ssize_t nbytes; /* The number of bytes received. */ uint16_t opcode; /* The received opcode */ uint16_t rblockno; /* The received block number */ int packetlen; /* Packet length */ int retry; /* Retry counter */ /* Try up to TFTP_RETRIES times */ for (retry = 0; retry < TFTP_RETRIES; retry++) { /* Try for until a valid ACK is received or some error occurs */ for (; ; ) { /* Receive the next UDP packet from the server */ nbytes = tftp_recvfrom(sd, packet, TFTP_IOBUFSIZE, &from); if (nbytes < TFTP_ACKHEADERSIZE) { /* Failed to receive a good packet */ if (nbytes == 0) { nerr("ERROR: Connection lost: %zd bytes\n", nbytes); } else if (nbytes > 0) { nerr("ERROR: Short packet: %zd bytes\n", nbytes); } else { nerr("ERROR: Recvfrom failure\n"); } /* Break out to bump up the retry count */ break; } else { /* Get the port being used by the server if that has not yet * been established. */ if (!*port) { *port = from.sin_port; server->sin_port = from.sin_port; } /* Verify that the packet was received from the correct host * and port. */ if (server->sin_addr.s_addr != from.sin_addr.s_addr) { ninfo("Invalid address in DATA\n"); continue; } if (*port != server->sin_port) { ninfo("Invalid port in DATA\n"); packetlen = tftp_mkerrpacket(packet, TFTP_ERR_UNKID, TFTP_ERRST_UNKID); tftp_sendto(sd, packet, packetlen, server); continue; } /* Parse the error message */ opcode = (uint16_t)packet[0] << 8 | (uint16_t)packet[1]; rblockno = (uint16_t)packet[2] << 8 | (uint16_t)packet[3]; /* Verify that the message that we received is an ACK for the * expected block number. */ if (opcode != TFTP_ACK) { nwarn("WARNING: Bad opcode\n"); #ifdef CONFIG_DEBUG_NET_WARN if (opcode == TFTP_ERR) { tftp_parseerrpacket(packet); } else #endif if (opcode > TFTP_MAXRFC1350) { packetlen = tftp_mkerrpacket(packet, TFTP_ERR_ILLEGALOP, TFTP_ERRST_ILLEGALOP); tftp_sendto(sd, packet, packetlen, server); } /* Break out an bump up the retry count */ break; } /* Success! */ ninfo("Received ACK for block %d\n", rblockno); *blockno = rblockno; return OK; } } } /* We have tried TFTP_RETRIES times */ nerr("ERROR: Timeout, Waiting for ACK\n"); return ERROR; /* Will never get here */ } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: tftpput_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 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 */ FAR uint8_t *packet; /* Allocated memory to hold one packet */ off_t offset; /* Offset into source file */ uint16_t blockno; /* The current transfer block number */ uint16_t rblockno; /* The ACK'ed block number */ uint16_t port = 0; /* This is the port nbr for the transfer */ int packetlen; /* The length of the data packet */ int sd; /* Socket descriptor for socket I/O */ int retry; /* Retry counter */ 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"); errno = ENOMEM; goto errout; } /* Initialize a UDP socket and setup the server address */ sd = tftp_sockinit(&server, addr); if (sd < 0) { goto errout_with_packet; } /* Send the write request using the well known port. This may need * to be done several times because (1) UDP is inherenly unreliable * and packets may be lost normally, and (2) uIP has a nasty habit * of droppying packets if there is nothing hit in the ARP table. */ blockno = 1; retry = 0; for (; ; ) { packetlen = tftp_mkreqpacket(packet, TFTP_WRQ, remote, binary); ret = tftp_sendto(sd, packet, packetlen, &server); if (ret != packetlen) { goto errout_with_sd; } /* Receive the ACK for the write request */ if (tftp_rcvack(sd, packet, &server, &port, NULL) == 0) { break; } nwarn("WARNING: Re-sending request\n"); /* We are going to loop and re-send the request packet. Check the * retry count so that we do not loop forever. */ if (++retry > TFTP_RETRIES) { nerr("ERROR: Retry count exceeded\n"); errno = ETIMEDOUT; goto errout_with_sd; } } /* Then loop sending the entire file to the server in chunks */ offset = 0; retry = 0; for (; ; ) { /* Construct the next data packet */ packetlen = tftp_mkdatapacket(offset, packet, blockno, cb, ctx); if (packetlen < 0) { goto errout_with_sd; } /* Send the next data chunk */ ret = tftp_sendto(sd, packet, packetlen, &server); if (ret != packetlen) { goto errout_with_sd; } /* Check for an ACK for the data chunk */ if (tftp_rcvack(sd, packet, &server, &port, &rblockno) == OK) { /* Check if the packet that we just sent was ACK'ed. If not, * we just loop to resend the same packet (same blockno, same * file offset). */ if (rblockno == blockno) { /* Yes.. If we are at the end of the file and if all of the * packets have been ACKed, then we are done. */ if (packetlen < TFTP_PACKETSIZE) { break; } /* Not the last block.. set up for the next block */ blockno += 1; offset += TFTP_DATASIZE; retry = 0; /* Skip the retry test */ continue; } } /* We are going to loop and re-send the data packet. Check the retry * count so that we do not loop forever. */ if (++retry > TFTP_RETRIES) { nerr("ERROR: Retry count exceeded\n"); errno = ETIMEDOUT; goto errout_with_sd; } } /* Return success */ result = OK; errout_with_sd: close(sd); errout_with_packet: free(packet); errout: return result; } /**************************************************************************** * Name: tftp_read ****************************************************************************/ static ssize_t tftp_read(FAR void *ctx, uint32_t offset, FAR uint8_t *buf, size_t buflen) { int fd = (intptr_t)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, (FAR void *)(intptr_t)fd); close(fd); errout: return result; } #endif /* CONFIG_NET && CONFIG_NET_UDP */