summaryrefslogblamecommitdiffstats
path: root/c/src/libnetworking/lib/tftpDriver.c
blob: e9564110f53be216ffdf32123f049421b0743120 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                                            
                   











                               





                                                  














































































































                                                                    











































                                                       


                             

                                                                            





                                          
 
                           



































































































































                                                                                







                                                               
  
                           
   




                                                               

 
                                    
                
                                   
 



                                 



                                                        







                                                                   




























                                                                      






























                                                                   































































































































































































































































                                                                                                    
/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/rtems_bsdnet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifndef set_errno_and_return_minus_one
#define set_errno_and_return_minus_one( _error ) \
  do { errno = (_error); return -1; } while(0)
#endif


/*
 * 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;

typedef struct {
   rtems_id                             tftp_mutex;
   int                                  nStreams;
   struct tftpStream ** volatile        tftpStreams;
  rtems_filesystem_mount_table_entry_t *mt_entry;  
}  tftp_fs_info;

int rtems_tftp_mount_me(
  rtems_filesystem_mount_table_entry_t *temp_mt_entry
)
{
  tftp_fs_info      *fs_info;
  rtems_status_code  sc;

  /*
   * Allocate stuff for this file system.
   */

  fs_info = calloc( 1, sizeof( tftp_fs_info ));
  if ( !fs_info )
    set_errno_and_return_minus_one( ENOMEM );

  temp_mt_entry->fs_info                = fs_info;
  temp_mt_entry->mt_fs_root.node_access = fs_info;

  
  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,
    &fs_info->tftp_mutex
  );

  if (sc != RTEMS_SUCCESSFUL)
    set_errno_and_return_minus_one( ENOMEM ); /* ??? */

  return 0;
}

/*
 * Initialize the TFTP driver
 */
/* XXX change name to rtems_bsdnet_initialize_tftp_filesystem ("mountpt") */
/* XXX this won't be a driver when we are finished */
rtems_device_driver rtems_tftp_initialize(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void *pargp
)
{

/* XXX change to a mount */
	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);
}

int rtems_tftp_evaluate_for_make(
   const char                         *path,       /* IN     */
   rtems_filesystem_location_info_t   *pathloc,    /* IN/OUT */
   const char                        **name        /* OUT    */
)
{  
    set_errno_and_return_minus_one( EIO );    
}
/*
 * XXX - Fix return values.
 */

int TFTP_eval_path(  
  const char                        *pathname,     /* IN     */
  int                                flags,        /* IN     */
  rtems_filesystem_location_info_t  *pathloc       /* IN/OUT */
)
{
	rtems_unsigned32 farAddress;
	int len;
	const char *remoteFilename;

  tftp_fs_info      *fs_info;
  const char *cp1, *cp2;

  fs_info = pathloc->node_access;

	/*
	 * Pick apart the name into a host:pathname pair
	 */
	/*
	 * XXX - I think this is handled by the caller of 
	 *       the evaluate routine. ? Am I starting at the right
	 *       place?
	 */

        cp2 = pathname+1;

	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;

  return 0;
}

/*
 * 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 = 0; /* XXX - node parameter */
	int s;
	int len;
	char *cp1, *cp2;
	char *remoteFilename = NULL; /* XXX - node parameter */
	rtems_interval now;
	rtems_status_code sc;

	/*
	 * Read-only for now
	 */
	/* XXX - Move to the open routine */
	if (ap->flags & LIBIO_FLAGS_WRITE)
		return RTEMS_NOT_IMPLEMENTED;


	/*
	 * 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;
}