From 39e6e65a2c5a3312f365d59f23c469641e049c82 Mon Sep 17 00:00:00 2001 From: Joel Sherrill Date: Wed, 19 Aug 1998 21:32:28 +0000 Subject: Base files --- cpukit/libnetworking/lib/tftpDriver.c | 609 ++++++++++++++++++++++++++++++++++ 1 file changed, 609 insertions(+) create mode 100644 cpukit/libnetworking/lib/tftpDriver.c (limited to 'cpukit/libnetworking/lib/tftpDriver.c') diff --git a/cpukit/libnetworking/lib/tftpDriver.c b/cpukit/libnetworking/lib/tftpDriver.c new file mode 100644 index 0000000000..cf75623580 --- /dev/null +++ b/cpukit/libnetworking/lib/tftpDriver.c @@ -0,0 +1,609 @@ +/* + * Trivial File Transfer Protocol (RFC 1350) + * + * Transfer file to/from remote host + * + * W. Eric Norum + * Saskatchewan Accelerator Laboratory + * University of Saskatchewan + * Saskatoon, Saskatchewan, CANADA + * eric@skatter.usask.ca + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Range of UDP ports to try + */ +#define UDP_PORT_BASE 3180 + +/* + * Pathname prefix + */ +#define TFTP_PATHNAME_PREFIX "/TFTP/" + +/* + * Default limits + */ +#define PACKET_REPLY_MILLISECONDS 6000 +#define OPEN_RETRY_LIMIT 10 +#define IO_RETRY_LIMIT 10 + +/* + * TFTP opcodes + */ +#define TFTP_OPCODE_RRQ 1 +#define TFTP_OPCODE_WRQ 2 +#define TFTP_OPCODE_DATA 3 +#define TFTP_OPCODE_ACK 4 +#define TFTP_OPCODE_ERROR 5 + +/* + * Largest data transfer + */ +#define TFTP_BUFSIZE 512 + +/* + * Packets transferred between machines + */ +union tftpPacket { + /* + * RRQ/WRQ packet + */ + struct tftpRWRQ { + rtems_unsigned16 opcode; + char filename_mode[TFTP_BUFSIZE]; + } tftpRWRQ; + + /* + * DATA packet + */ + struct tftpDATA { + rtems_unsigned16 opcode; + rtems_unsigned16 blocknum; + rtems_unsigned8 data[TFTP_BUFSIZE]; + } tftpDATA; + + /* + * ACK packet + */ + struct tftpACK { + rtems_unsigned16 opcode; + rtems_unsigned16 blocknum; + } tftpACK; + + /* + * ERROR packet + */ + struct tftpERROR { + rtems_unsigned16 opcode; + rtems_unsigned16 errorCode; + char errorMessage[TFTP_BUFSIZE]; + } tftpERROR; +}; + +/* + * State of each TFTP stream + */ +struct tftpStream { + /* + * Buffer for storing most recently-received packet + */ + union tftpPacket pkbuf; + + /* + * Last block number received + */ + rtems_unsigned16 blocknum; + + /* + * Data transfer socket + */ + int socket; + struct sockaddr_in myAddress; + struct sockaddr_in farAddress; + + /* + * Indices into buffer + */ + int nleft; + int nused; + + /* + * Flags + */ + int firstReply; + int eof; +}; + +/* + * Number of streams open at the same time + */ +static rtems_id tftp_mutex; +static int nStreams; +static struct tftpStream ** volatile tftpStreams; + +/* + * Initialize the TFTP driver + */ +rtems_device_driver rtems_tftp_initialize( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *pargp +) +{ + rtems_status_code sc; + + sc = rtems_semaphore_create (rtems_build_name('T', 'F', 'T', 'P'), + 1, + RTEMS_FIFO | + RTEMS_BINARY_SEMAPHORE | + RTEMS_NO_INHERIT_PRIORITY | + RTEMS_NO_PRIORITY_CEILING | + RTEMS_LOCAL, + 0, + &tftp_mutex); + if (sc != RTEMS_SUCCESSFUL) + return sc; + rtems_io_register_name (TFTP_PATHNAME_PREFIX, major, minor); + return RTEMS_SUCCESSFUL; +} + +/* + * Set error message + * This RTEMS/UNIX error mapping needs to be fixed! + */ +static void +tftpSetErrno (struct tftpStream *tp) +{ + unsigned int tftpError; + static const int errorMap[] = { + 0, + ENOENT, + EPERM, + ENOSPC, + EINVAL, + ENXIO, + EEXIST, + ESRCH, + 0, + }; + + tftpError = ntohs (tp->pkbuf.tftpERROR.errorCode); + if (tftpError < (sizeof errorMap / sizeof errorMap[0])) + errno = errorMap[tftpError]; + else + errno = 1000 + tftpError; +} + +/* + * Send a message to make the other end shut up + */ +static void +sendStifle (struct tftpStream *tp, struct sockaddr_in *to) +{ + int len; + + /* + * Create the error packet (Unknown transfer ID). + */ + tp->pkbuf.tftpERROR.opcode = htons (TFTP_OPCODE_ERROR); + tp->pkbuf.tftpERROR.errorCode = htons (5); + len = sizeof tp->pkbuf.tftpERROR.opcode + + sizeof tp->pkbuf.tftpERROR.errorCode + 1; + len += sprintf (tp->pkbuf.tftpERROR.errorMessage, "GO AWAY"); + + /* + * Send it + */ + sendto (tp->socket, (char *)&tp->pkbuf, len, 0, + (struct sockaddr *)to, sizeof *to); +} + +/* + * Wait for a data packet + */ +static int +getPacket (struct tftpStream *tp) +{ + int len; + struct timeval tv; + + tv.tv_sec = 6; + tv.tv_usec = 0; + setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); + for (;;) { + union { + struct sockaddr s; + struct sockaddr_in i; + } from; + int fromlen = sizeof from; + len = recvfrom (tp->socket, (char *)&tp->pkbuf, + sizeof tp->pkbuf, 0, + &from.s, &fromlen); + if (len < 0) + break; + if (from.i.sin_addr.s_addr == tp->farAddress.sin_addr.s_addr) { + if (tp->firstReply) { + tp->firstReply = 0; + tp->farAddress.sin_port = from.i.sin_port; + } + if (tp->farAddress.sin_port == from.i.sin_port) + break; + } + + /* + * Packet is from someone with whom we are + * not interested. Tell them to go away. + */ + sendStifle (tp, &from.i); + } + tv.tv_sec = 0; + setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); + return len; +} + +/* + * Send an acknowledgement + */ +static int +sendAck (struct tftpStream *tp) +{ + /* + * Create the acknowledgement + */ + tp->pkbuf.tftpACK.opcode = htons (TFTP_OPCODE_ACK); + tp->pkbuf.tftpACK.blocknum = htons (tp->blocknum); + + /* + * Send it + */ + if (sendto (tp->socket, (char *)&tp->pkbuf, sizeof tp->pkbuf.tftpACK, 0, + (struct sockaddr *)&tp->farAddress, + sizeof tp->farAddress) < 0) + return errno; + return 0; +} + +/* + * Release a stream and clear the pointer to it + */ +static void +releaseStream (int s) +{ + rtems_semaphore_obtain (tftp_mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + free (tftpStreams[s]); + tftpStreams[s] = NULL; + rtems_semaphore_release (tftp_mutex); +} + +/* + * Open a TFTP stream + */ +rtems_device_driver rtems_tftp_open( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *pargp +) +{ + rtems_libio_open_close_args_t *ap = pargp; + struct tftpStream *tp; + int retryCount; + rtems_unsigned32 farAddress; + int s; + int len; + char *cp1, *cp2; + char *remoteFilename; + rtems_interval now; + rtems_status_code sc; + + /* + * Read-only for now + */ + if (ap->flags & LIBIO_FLAGS_WRITE) + return RTEMS_NOT_IMPLEMENTED; + + /* + * Pick apart the name into a host:pathname pair + */ + if (strlen (ap->iop->pathname) <= strlen (TFTP_PATHNAME_PREFIX)) + return RTEMS_INVALID_NAME; + cp2 = ap->iop->pathname + strlen (TFTP_PATHNAME_PREFIX); + if (*cp2 == '/') { + farAddress = rtems_bsdnet_bootp_server_address.s_addr; + } + else { + char *hostname; + + cp1 = cp2; + while (*cp2 != '/') { + if (*cp2 == '\0') + return RTEMS_INVALID_NAME; + cp2++; + } + len = cp2 - cp1; + hostname = malloc (len + 1); + if (hostname == NULL) + return RTEMS_NO_MEMORY; + strncpy (hostname, cp1, len); + hostname[len] = '\0'; + farAddress = inet_addr (hostname); + free (hostname); + } + if ((farAddress == 0) || (farAddress == ~0)) + return RTEMS_INVALID_NAME; + if (*++cp2 == '\0') + return RTEMS_INVALID_NAME; + remoteFilename = cp2; + if (strlen (remoteFilename) > (TFTP_BUFSIZE - 10)) + return RTEMS_INVALID_NAME; + + /* + * Find a free stream + */ + sc = rtems_semaphore_obtain (tftp_mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + if (sc != RTEMS_SUCCESSFUL) + return sc; + for (s = 0 ; s < nStreams ; s++) { + if (tftpStreams[s] == NULL) + break; + } + if (s == nStreams) { + /* + * Reallocate stream pointers + * Guard against the case where realloc() returns NULL. + */ + struct tftpStream **np; + + np = realloc (tftpStreams, ++nStreams * sizeof *tftpStreams); + if (np == NULL) { + rtems_semaphore_release (tftp_mutex); + return RTEMS_NO_MEMORY; + } + tftpStreams = np; + } + tp = tftpStreams[s] = malloc (sizeof (struct tftpStream)); + rtems_semaphore_release (tftp_mutex); + if (tp == NULL) + return RTEMS_NO_MEMORY; + ap->iop->data0 = s; + ap->iop->data1 = tp; + + /* + * Create the socket + */ + if ((tp->socket = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { + releaseStream (s); + return RTEMS_TOO_MANY; + } + + /* + * Bind the socket to a local address + */ + retryCount = 0; + rtems_clock_get (RTEMS_CLOCK_GET_TICKS_SINCE_BOOT, &now); + for (;;) { + int try = (now + retryCount) % 10; + + tp->myAddress.sin_family = AF_INET; + tp->myAddress.sin_port = htons (UDP_PORT_BASE + nStreams * try + minor); + tp->myAddress.sin_addr.s_addr = htonl (INADDR_ANY); + if (bind (tp->socket, (struct sockaddr *)&tp->myAddress, sizeof tp->myAddress) >= 0) + break; + if (++retryCount == 10) { + close (tp->socket); + releaseStream (minor); + return RTEMS_RESOURCE_IN_USE; + } + } + + /* + * Set the UDP destination to the TFTP server + * port on the remote machine. + */ + tp->farAddress.sin_family = AF_INET; + tp->farAddress.sin_addr.s_addr = farAddress; + tp->farAddress.sin_port = htons (69); + + /* + * Start the transfer + */ + tp->firstReply = 1; + for (;;) { + /* + * Create the request + */ + tp->pkbuf.tftpRWRQ.opcode = htons (TFTP_OPCODE_RRQ); + cp1 = tp->pkbuf.tftpRWRQ.filename_mode; + cp2 = remoteFilename; + while ((*cp1++ = *cp2++) != '\0') + continue; + cp2 = "octet"; + while ((*cp1++ = *cp2++) != '\0') + continue; + len = cp1 - (char *)&tp->pkbuf.tftpRWRQ; + + /* + * Send the request + */ + if (sendto (tp->socket, (char *)&tp->pkbuf, len, 0, + (struct sockaddr *)&tp->farAddress, + sizeof tp->farAddress) < 0) { + close (tp->socket); + releaseStream (minor); + return RTEMS_UNSATISFIED; + } + + /* + * Get reply + */ + len = getPacket (tp); + if (len >= (int) sizeof tp->pkbuf.tftpACK) { + int opcode = ntohs (tp->pkbuf.tftpDATA.opcode); + if ((opcode == TFTP_OPCODE_DATA) + && (ntohs (tp->pkbuf.tftpDATA.blocknum) == 1)) { + tp->nused = 0; + tp->blocknum = 1; + tp->nleft = len - 2 * sizeof (rtems_unsigned16); + tp->eof = (tp->nleft < TFTP_BUFSIZE); + if (sendAck (tp) != 0) { + close (tp->socket); + releaseStream (minor); + return RTEMS_UNSATISFIED; + } + break; + } + if (opcode == TFTP_OPCODE_ERROR) { + tftpSetErrno (tp); + close (tp->socket); + releaseStream (ap->iop->data0); + return RTEMS_INTERNAL_ERROR; + } + } + + /* + * Keep trying + */ + if (++retryCount >= OPEN_RETRY_LIMIT) { + close (tp->socket); + releaseStream (minor); + return RTEMS_UNSATISFIED; + } + } + return RTEMS_SUCCESSFUL; +} + +/* + * Read from a TFTP stream + */ +rtems_device_driver rtems_tftp_read( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *pargp +) +{ + rtems_libio_rw_args_t *ap = pargp; + char *bp; + struct tftpStream *tp; + int retryCount; + int nwant; + + tp = ap->iop->data1; + + /* + * Read till user request is satisfied or EOF is reached + */ + bp = ap->buffer; + nwant = ap->count; + while (nwant) { + if (tp->nleft) { + int count; + if (nwant < tp->nleft) + count = nwant; + else + count = tp->nleft; + memcpy (bp, &tp->pkbuf.tftpDATA.data[tp->nused], count); + tp->nused += count; + tp->nleft -= count; + bp += count; + nwant -= count; + if (nwant == 0) + break; + } + if (tp->eof) + break; + + /* + * Wait for the next packet + */ + retryCount = 0; + for (;;) { + int len = getPacket (tp); + if (len >= (int)sizeof tp->pkbuf.tftpACK) { + int opcode = ntohs (tp->pkbuf.tftpDATA.opcode); + rtems_unsigned16 nextBlock = tp->blocknum + 1; + if ((opcode == TFTP_OPCODE_DATA) + && (ntohs (tp->pkbuf.tftpDATA.blocknum) == nextBlock)) { + tp->nused = 0; + tp->nleft = len - 2 * sizeof (rtems_unsigned16); + tp->eof = (tp->nleft < TFTP_BUFSIZE); + tp->blocknum++; + if (sendAck (tp) != 0) + return RTEMS_IO_ERROR; + break; + } + if (opcode == TFTP_OPCODE_ERROR) { + tftpSetErrno (tp); + return RTEMS_INTERNAL_ERROR; + } + } + + /* + * Keep trying? + */ + if (++retryCount == IO_RETRY_LIMIT) + return RTEMS_IO_ERROR; + if (sendAck (tp) != 0) + return RTEMS_IO_ERROR; + } + } + ap->bytes_moved = ap->count - nwant; + return RTEMS_SUCCESSFUL; +} + +/* + * Close a TFTP stream + */ +rtems_device_driver rtems_tftp_close( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *pargp +) +{ + rtems_libio_open_close_args_t *ap = pargp; + struct tftpStream *tp = ap->iop->data1;; + + if (!tp->eof && !tp->firstReply) { + /* + * Tell the other end to stop + */ + rtems_interval ticksPerSecond; + sendStifle (tp, &tp->farAddress); + rtems_clock_get (RTEMS_CLOCK_GET_TICKS_PER_SECOND, &ticksPerSecond); + rtems_task_wake_after (1 + ticksPerSecond / 10); + } + close (tp->socket); + releaseStream (ap->iop->data0); + return RTEMS_SUCCESSFUL; +} + +rtems_device_driver rtems_tftp_write( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *pargp +) +{ + return RTEMS_NOT_CONFIGURED; +} + +rtems_device_driver rtems_tftp_control( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *pargp +) +{ + return RTEMS_NOT_CONFIGURED; +} -- cgit v1.2.3