From a28eafb2a4d7418ad1efb3f8b72e9cbb014c1c0b Mon Sep 17 00:00:00 2001 From: Vijay Kumar Banerjee Date: Wed, 24 Feb 2021 12:27:29 -0700 Subject: cpukit: Move ftpfs from libnetworking to libfs Update #3850 --- cpukit/libfs/src/ftpfs/tftpDriver.c | 1066 +++++++++++++++++++++++++++++++++++ 1 file changed, 1066 insertions(+) create mode 100644 cpukit/libfs/src/ftpfs/tftpDriver.c (limited to 'cpukit/libfs/src/ftpfs/tftpDriver.c') diff --git a/cpukit/libfs/src/ftpfs/tftpDriver.c b/cpukit/libfs/src/ftpfs/tftpDriver.c new file mode 100644 index 0000000000..7cbb402b63 --- /dev/null +++ b/cpukit/libfs/src/ftpfs/tftpDriver.c @@ -0,0 +1,1066 @@ +/* + * 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 + * + * Modifications to support reference counting in the file system are + * Copyright (c) 2012 embedded brains GmbH. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef RTEMS_NETWORKING +#include +#endif + +#ifdef RTEMS_TFTP_DRIVER_DEBUG +int rtems_tftp_driver_debug = 1; +#endif + +/* + * Range of UDP ports to try + */ +#define UDP_PORT_BASE 3180 + +/* + * Default limits + */ +#define PACKET_FIRST_TIMEOUT_MILLISECONDS 400L +#define PACKET_TIMEOUT_MILLISECONDS 6000L +#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 { + uint16_t opcode; + char filename_mode[TFTP_BUFSIZE]; + } tftpRWRQ; + + /* + * DATA packet + */ + struct tftpDATA { + uint16_t opcode; + uint16_t blocknum; + uint8_t data[TFTP_BUFSIZE]; + } tftpDATA; + + /* + * ACK packet + */ + struct tftpACK { + uint16_t opcode; + uint16_t blocknum; + } tftpACK; + + /* + * ERROR packet + */ + struct tftpERROR { + uint16_t opcode; + uint16_t 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 transferred + */ + uint16_t 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; + int writing; +}; + +/* + * Flags for filesystem info. + */ +#define TFTPFS_VERBOSE (1 << 0) + +/* + * TFTP File system info. + */ +typedef struct tftpfs_info_s { + uint32_t flags; + rtems_mutex tftp_mutex; + int nStreams; + struct tftpStream ** volatile tftpStreams; +} tftpfs_info_t; + +#define tftpfs_info_mount_table(_mt) ((tftpfs_info_t*) ((_mt)->fs_info)) +#define tftpfs_info_pathloc(_pl) ((tftpfs_info_t*) ((_pl)->mt_entry->fs_info)) +#define tftpfs_info_iop(_iop) (tftpfs_info_pathloc (&((_iop)->pathinfo))) + +/* + * Number of streams open at the same time + */ + +static const rtems_filesystem_operations_table rtems_tftp_ops; +static const rtems_filesystem_file_handlers_r rtems_tftp_handlers; + +static bool rtems_tftp_is_directory( + const char *path, + size_t pathlen +) +{ + return path [pathlen - 1] == '/'; +} + +int rtems_tftpfs_initialize( + rtems_filesystem_mount_table_entry_t *mt_entry, + const void *data +) +{ + const char *device = mt_entry->dev; + size_t devicelen = strlen (device); + tftpfs_info_t *fs = NULL; + char *root_path; + + if (devicelen == 0) { + root_path = malloc (1); + if (root_path == NULL) + goto error; + root_path [0] = '\0'; + } + else { + root_path = malloc (devicelen + 2); + if (root_path == NULL) + goto error; + + root_path = memcpy (root_path, device, devicelen); + root_path [devicelen] = '/'; + root_path [devicelen + 1] = '\0'; + } + + fs = malloc (sizeof (*fs)); + if (fs == NULL) + goto error; + fs->flags = 0; + fs->nStreams = 0; + fs->tftpStreams = 0; + + mt_entry->fs_info = fs; + mt_entry->mt_fs_root->location.node_access = root_path; + mt_entry->mt_fs_root->location.handlers = &rtems_tftp_handlers; + mt_entry->ops = &rtems_tftp_ops; + + /* + * Now allocate a semaphore for mutual exclusion. + * + * NOTE: This could be in an fsinfo for this filesystem type. + */ + + rtems_mutex_init (&fs->tftp_mutex, "TFTPFS"); + + if (data) { + char* config = (char*) data; + char* token; + char* saveptr; + token = strtok_r (config, " ", &saveptr); + while (token) { + if (strcmp (token, "verbose") == 0) + fs->flags |= TFTPFS_VERBOSE; + token = strtok_r (NULL, " ", &saveptr); + } + } + + return 0; + +error: + + free (fs); + free (root_path); + + rtems_set_errno_and_return_minus_one (ENOMEM); +} + +/* + * Release a stream and clear the pointer to it + */ +static void +releaseStream (tftpfs_info_t *fs, int s) +{ + if (fs->tftpStreams[s] && (fs->tftpStreams[s]->socket >= 0)) + close (fs->tftpStreams[s]->socket); + rtems_mutex_lock (&fs->tftp_mutex); + free (fs->tftpStreams[s]); + fs->tftpStreams[s] = NULL; + rtems_mutex_unlock (&fs->tftp_mutex); +} + +static void +rtems_tftpfs_shutdown (rtems_filesystem_mount_table_entry_t* mt_entry) +{ + tftpfs_info_t *fs = tftpfs_info_mount_table (mt_entry); + int s; + for (s = 0; s < fs->nStreams; s++) + releaseStream (fs, s); + rtems_mutex_destroy (&fs->tftp_mutex); + free (fs); + free (mt_entry->mt_fs_root->location.node_access); +} + +/* + * Map error message + */ +static int +tftpErrno (struct tftpStream *tp) +{ + unsigned int tftpError; + static const int errorMap[] = { + EINVAL, + ENOENT, + EPERM, + ENOSPC, + EINVAL, + ENXIO, + EEXIST, + ESRCH, + }; + + tftpError = ntohs (tp->pkbuf.tftpERROR.errorCode); + if (tftpError < (sizeof errorMap / sizeof errorMap[0])) + return errorMap[tftpError]; + else + return 1000 + tftpError; +} + +/* + * Send a message to make the other end shut up + */ +static void +sendStifle (struct tftpStream *tp, struct sockaddr_in *to) +{ + int len; + struct { + uint16_t opcode; + uint16_t errorCode; + char errorMessage[12]; + } msg; + + /* + * Create the error packet (Unknown transfer ID). + */ + msg.opcode = htons (TFTP_OPCODE_ERROR); + msg.errorCode = htons (5); + len = sizeof msg.opcode + sizeof msg.errorCode + 1; + len += sprintf (msg.errorMessage, "GO AWAY"); + + /* + * Send it + */ + sendto (tp->socket, (char *)&msg, len, 0, (struct sockaddr *)to, sizeof *to); +} + +/* + * Wait for a data packet + */ +static int +getPacket (struct tftpStream *tp, int retryCount) +{ + int len; + struct timeval tv; + + if (retryCount == 0) { + tv.tv_sec = PACKET_FIRST_TIMEOUT_MILLISECONDS / 1000L; + tv.tv_usec = (PACKET_FIRST_TIMEOUT_MILLISECONDS % 1000L) * 1000L; + } + else { + tv.tv_sec = PACKET_TIMEOUT_MILLISECONDS / 1000L; + tv.tv_usec = (PACKET_TIMEOUT_MILLISECONDS % 1000L) * 1000L; + } + setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); + for (;;) { + union { + struct sockaddr s; + struct sockaddr_in i; + } from; + socklen_t fromlen = sizeof from; + len = recvfrom (tp->socket, &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; + tv.tv_usec = 0; + setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); +#ifdef RTEMS_TFTP_DRIVER_DEBUG + if (rtems_tftp_driver_debug) { + if (len >= (int) sizeof tp->pkbuf.tftpACK) { + int opcode = ntohs (tp->pkbuf.tftpDATA.opcode); + switch (opcode) { + default: + printf ("TFTP: OPCODE %d\n", opcode); + break; + + case TFTP_OPCODE_DATA: + printf ("TFTP: RECV %d\n", ntohs (tp->pkbuf.tftpDATA.blocknum)); + break; + + case TFTP_OPCODE_ACK: + printf ("TFTP: GOT ACK %d\n", ntohs (tp->pkbuf.tftpACK.blocknum)); + break; + } + } + else { + printf ("TFTP: %d-byte packet\n", len); + } + } +#endif + return len; +} + +/* + * Send an acknowledgement + */ +static int +sendAck (struct tftpStream *tp) +{ +#ifdef RTEMS_TFTP_DRIVER_DEBUG + if (rtems_tftp_driver_debug) + printf ("TFTP: ACK %d\n", tp->blocknum); +#endif + + /* + * 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; +} + +/* + * Convert a path to canonical form + */ +static void +fixPath (char *path) +{ + char *inp, *outp, *base; + + outp = inp = path; + base = NULL; + for (;;) { + if (inp[0] == '.') { + if (inp[1] == '\0') + break; + if (inp[1] == '/') { + inp += 2; + continue; + } + if (inp[1] == '.') { + if (inp[2] == '\0') { + if ((base != NULL) && (outp > base)) { + outp--; + while ((outp > base) && (outp[-1] != '/')) + outp--; + } + break; + } + if (inp[2] == '/') { + inp += 3; + if (base == NULL) + continue; + if (outp > base) { + outp--; + while ((outp > base) && (outp[-1] != '/')) + outp--; + } + continue; + } + } + } + if (base == NULL) + base = inp; + while (inp[0] != '/') { + if ((*outp++ = *inp++) == '\0') + return; + } + *outp++ = '/'; + while (inp[0] == '/') + inp++; + } + *outp = '\0'; + return; +} + +static void rtems_tftp_eval_path(rtems_filesystem_eval_path_context_t *self) +{ + int eval_flags = rtems_filesystem_eval_path_get_flags (self); + + if ((eval_flags & RTEMS_FS_MAKE) == 0) { + int rw = RTEMS_FS_PERMS_READ | RTEMS_FS_PERMS_WRITE; + + if ((eval_flags & rw) != rw) { + rtems_filesystem_location_info_t *currentloc = + rtems_filesystem_eval_path_get_currentloc (self); + char *current = currentloc->node_access; + size_t currentlen = strlen (current); + const char *path = rtems_filesystem_eval_path_get_path (self); + size_t pathlen = rtems_filesystem_eval_path_get_pathlen (self); + size_t len = currentlen + pathlen; + + rtems_filesystem_eval_path_clear_path (self); + + current = realloc (current, len + 1); + if (current != NULL) { + memcpy (current + currentlen, path, pathlen); + current [len] = '\0'; + if (!rtems_tftp_is_directory (current, len)) { + fixPath (current); + } + currentloc->node_access = current; + } else { + rtems_filesystem_eval_path_error (self, ENOMEM); + } + } else { + rtems_filesystem_eval_path_error (self, EINVAL); + } + } else { + rtems_filesystem_eval_path_error (self, EIO); + } +} + +/* + * The routine which does most of the work for the IMFS open handler + */ +static int rtems_tftp_open_worker( + rtems_libio_t *iop, + char *full_path_name, + int oflag +) +{ + tftpfs_info_t *fs; + struct tftpStream *tp; + int retryCount; + struct in_addr farAddress; + int s; + int len; + char *cp1; + char *cp2; + char *remoteFilename; + rtems_interval now; + char *hostname; + + /* + * Get the file system info. + */ + fs = tftpfs_info_iop (iop); + + /* + * Extract the host name component + */ + if (*full_path_name == '/') + full_path_name++; + + hostname = full_path_name; + cp1 = strchr (full_path_name, ':'); + if (!cp1) { +#ifdef RTEMS_NETWORKING + hostname = "BOOTP_HOST"; +#endif + } else { + *cp1 = '\0'; + ++cp1; + } + + /* + * Convert hostname to Internet address + */ +#ifdef RTEMS_NETWORKING + if (strcmp (hostname, "BOOTP_HOST") == 0) + farAddress = rtems_bsdnet_bootp_server_address; + else +#endif + if (inet_aton (hostname, &farAddress) == 0) { + struct hostent *he = gethostbyname(hostname); + if (he == NULL) + return ENOENT; + memcpy (&farAddress, he->h_addr, sizeof (farAddress)); + } + + /* + * Extract file pathname component + */ +#ifdef RTEMS_NETWORKING + if (strcmp (cp1, "BOOTP_FILE") == 0) { + cp1 = rtems_bsdnet_bootp_boot_file_name; + } +#endif + if (*cp1 == '\0') + return ENOENT; + remoteFilename = cp1; + if (strlen (remoteFilename) > (TFTP_BUFSIZE - 10)) + return ENOENT; + + /* + * Find a free stream + */ + rtems_mutex_lock (&fs->tftp_mutex); + for (s = 0 ; s < fs->nStreams ; s++) { + if (fs->tftpStreams[s] == NULL) + break; + } + if (s == fs->nStreams) { + /* + * Reallocate stream pointers + * Guard against the case where realloc() returns NULL. + */ + struct tftpStream **np; + + np = realloc (fs->tftpStreams, ++fs->nStreams * sizeof *fs->tftpStreams); + if (np == NULL) { + rtems_mutex_unlock (&fs->tftp_mutex); + return ENOMEM; + } + fs->tftpStreams = np; + } + tp = fs->tftpStreams[s] = malloc (sizeof (struct tftpStream)); + rtems_mutex_unlock (&fs->tftp_mutex); + if (tp == NULL) + return ENOMEM; + iop->data0 = s; + iop->data1 = tp; + + /* + * Create the socket + */ + if ((tp->socket = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { + releaseStream (fs, s); + return ENOMEM; + } + + /* + * Bind the socket to a local address + */ + retryCount = 0; + now = rtems_clock_get_ticks_since_boot(); + for (;;) { + int try = (now + retryCount) % 10; + + tp->myAddress.sin_family = AF_INET; + tp->myAddress.sin_port = htons (UDP_PORT_BASE + fs->nStreams * try + s); + 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) { + releaseStream (fs, s); + return EBUSY; + } + } + + /* + * Set the UDP destination to the TFTP server + * port on the remote machine. + */ + tp->farAddress.sin_family = AF_INET; + tp->farAddress.sin_addr = farAddress; + tp->farAddress.sin_port = htons (69); + + /* + * Start the transfer + */ + tp->firstReply = 1; + retryCount = 0; + for (;;) { + /* + * Create the request + */ + if ((oflag & O_ACCMODE) == O_RDONLY) { + tp->writing = 0; + tp->pkbuf.tftpRWRQ.opcode = htons (TFTP_OPCODE_RRQ); + } + else { + tp->writing = 1; + tp->pkbuf.tftpRWRQ.opcode = htons (TFTP_OPCODE_WRQ); + } + cp1 = (char *) tp->pkbuf.tftpRWRQ.filename_mode; + cp2 = (char *) 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) { + releaseStream (fs, s); + return EIO; + } + + /* + * Get reply + */ + len = getPacket (tp, retryCount); + if (len >= (int) sizeof tp->pkbuf.tftpACK) { + int opcode = ntohs (tp->pkbuf.tftpDATA.opcode); + if (!tp->writing + && (opcode == TFTP_OPCODE_DATA) + && (ntohs (tp->pkbuf.tftpDATA.blocknum) == 1)) { + tp->nused = 0; + tp->blocknum = 1; + tp->nleft = len - 2 * sizeof (uint16_t ); + tp->eof = (tp->nleft < TFTP_BUFSIZE); + if (sendAck (tp) != 0) { + releaseStream (fs, s); + return EIO; + } + break; + } + if (tp->writing + && (opcode == TFTP_OPCODE_ACK) + && (ntohs (tp->pkbuf.tftpACK.blocknum) == 0)) { + tp->nused = 0; + tp->blocknum = 1; + break; + } + if (opcode == TFTP_OPCODE_ERROR) { + int e = tftpErrno (tp); + releaseStream (fs, s); + return e; + } + } + + /* + * Keep trying + */ + if (++retryCount >= OPEN_RETRY_LIMIT) { + releaseStream (fs, s); + return EIO; + } + } + return 0; +} + +static int rtems_tftp_open( + rtems_libio_t *iop, + const char *new_name, + int oflag, + mode_t mode +) +{ + tftpfs_info_t *fs; + char *full_path_name; + int err; + + full_path_name = iop->pathinfo.node_access; + + if (rtems_tftp_is_directory (full_path_name, strlen (full_path_name))) { + rtems_set_errno_and_return_minus_one (ENOTSUP); + } + + /* + * Get the file system info. + */ + fs = tftpfs_info_iop (iop); + + if (fs->flags & TFTPFS_VERBOSE) + printf ("TFTPFS: %s\n", full_path_name); + + err = rtems_tftp_open_worker (iop, full_path_name, oflag); + if (err != 0) { + rtems_set_errno_and_return_minus_one (err); + } + + return 0; +} + +/* + * Read from a TFTP stream + */ +static ssize_t rtems_tftp_read( + rtems_libio_t *iop, + void *buffer, + size_t count +) +{ + char *bp; + struct tftpStream *tp = iop->data1; + int retryCount; + int nwant; + + if (!tp) + rtems_set_errno_and_return_minus_one( EIO ); + + /* + * Read till user request is satisfied or EOF is reached + */ + bp = buffer; + nwant = count; + while (nwant) { + if (tp->nleft) { + int ncopy; + if (nwant < tp->nleft) + ncopy = nwant; + else + ncopy = tp->nleft; + memcpy (bp, &tp->pkbuf.tftpDATA.data[tp->nused], ncopy); + tp->nused += ncopy; + tp->nleft -= ncopy; + bp += ncopy; + nwant -= ncopy; + if (nwant == 0) + break; + } + if (tp->eof) + break; + + /* + * Wait for the next packet + */ + retryCount = 0; + for (;;) { + int len = getPacket (tp, retryCount); + if (len >= (int)sizeof tp->pkbuf.tftpACK) { + int opcode = ntohs (tp->pkbuf.tftpDATA.opcode); + uint16_t nextBlock = tp->blocknum + 1; + if ((opcode == TFTP_OPCODE_DATA) + && (ntohs (tp->pkbuf.tftpDATA.blocknum) == nextBlock)) { + tp->nused = 0; + tp->nleft = len - 2 * sizeof (uint16_t); + tp->eof = (tp->nleft < TFTP_BUFSIZE); + tp->blocknum++; + if (sendAck (tp) != 0) + rtems_set_errno_and_return_minus_one (EIO); + break; + } + if (opcode == TFTP_OPCODE_ERROR) + rtems_set_errno_and_return_minus_one (tftpErrno (tp)); + } + + /* + * Keep trying? + */ + if (++retryCount == IO_RETRY_LIMIT) + rtems_set_errno_and_return_minus_one (EIO); + if (sendAck (tp) != 0) + rtems_set_errno_and_return_minus_one (EIO); + } + } + return count - nwant; +} + +/* + * Flush a write buffer and wait for acknowledgement + */ +static int rtems_tftp_flush (struct tftpStream *tp) +{ + int wlen, rlen; + int retryCount = 0; + + wlen = tp->nused + 2 * sizeof (uint16_t ); + for (;;) { + tp->pkbuf.tftpDATA.opcode = htons (TFTP_OPCODE_DATA); + tp->pkbuf.tftpDATA.blocknum = htons (tp->blocknum); +#ifdef RTEMS_TFTP_DRIVER_DEBUG + if (rtems_tftp_driver_debug) + printf ("TFTP: SEND %d (%d)\n", tp->blocknum, tp->nused); +#endif + if (sendto (tp->socket, (char *)&tp->pkbuf, wlen, 0, + (struct sockaddr *)&tp->farAddress, + sizeof tp->farAddress) < 0) + return EIO; + rlen = getPacket (tp, retryCount); + /* + * Our last packet won't necessarily be acknowledged! + */ + if ((rlen < 0) && (tp->nused < sizeof tp->pkbuf.tftpDATA.data)) + return 0; + if (rlen >= (int)sizeof tp->pkbuf.tftpACK) { + int opcode = ntohs (tp->pkbuf.tftpACK.opcode); + if ((opcode == TFTP_OPCODE_ACK) + && (ntohs (tp->pkbuf.tftpACK.blocknum) == tp->blocknum)) { + tp->nused = 0; + tp->blocknum++; + return 0; + } + if (opcode == TFTP_OPCODE_ERROR) + return tftpErrno (tp); + } + + /* + * Keep trying? + */ + if (++retryCount == IO_RETRY_LIMIT) + return EIO; + } +} + +/* + * Close a TFTP stream + */ +static int rtems_tftp_close( + rtems_libio_t *iop +) +{ + tftpfs_info_t *fs; + struct tftpStream *tp = iop->data1; + int e = 0; + + /* + * Get the file system info. + */ + fs = tftpfs_info_iop (iop); + + if (!tp) + rtems_set_errno_and_return_minus_one (EIO); + + if (tp->writing) + e = rtems_tftp_flush (tp); + if (!tp->eof && !tp->firstReply) { + /* + * Tell the other end to stop + */ + rtems_interval ticksPerSecond; + sendStifle (tp, &tp->farAddress); + ticksPerSecond = rtems_clock_get_ticks_per_second(); + rtems_task_wake_after (1 + ticksPerSecond / 10); + } + releaseStream (fs, iop->data0); + if (e) + rtems_set_errno_and_return_minus_one (e); + return 0; +} + +static ssize_t rtems_tftp_write( + rtems_libio_t *iop, + const void *buffer, + size_t count +) +{ + const char *bp; + struct tftpStream *tp = iop->data1; + int nleft, nfree, ncopy; + + /* + * Bail out if an error has occurred + */ + if (!tp->writing) + rtems_set_errno_and_return_minus_one (EIO); + + /* + * Write till user request is satisfied + * Notice that the buffer is flushed as soon as it is filled rather + * than waiting for the next write or a close. This ensures that + * the flush in close writes a less than full buffer so the far + * end can detect the end-of-file condition. + */ + bp = buffer; + nleft = count; + while (nleft) { + nfree = sizeof tp->pkbuf.tftpDATA.data - tp->nused; + if (nleft < nfree) + ncopy = nleft; + else + ncopy = nfree; + memcpy (&tp->pkbuf.tftpDATA.data[tp->nused], bp, ncopy); + tp->nused += ncopy; + nleft -= ncopy; + bp += ncopy; + if (tp->nused == sizeof tp->pkbuf.tftpDATA.data) { + int e = rtems_tftp_flush (tp); + if (e) { + tp->writing = 0; + rtems_set_errno_and_return_minus_one (e); + } + } + } + return count; +} + +/* + * Dummy version to let fopen(xxxx,"w") work properly. + */ +static int rtems_tftp_ftruncate( + rtems_libio_t *iop RTEMS_UNUSED, + off_t count RTEMS_UNUSED +) +{ + return 0; +} + +static int rtems_tftp_fstat( + const rtems_filesystem_location_info_t *loc, + struct stat *buf +) +{ + const char *path = loc->node_access; + size_t pathlen = strlen (path); + + buf->st_mode = S_IRWXU | S_IRWXG | S_IRWXO + | (rtems_tftp_is_directory (path, pathlen) ? S_IFDIR : S_IFREG); + + return 0; +} + +static int rtems_tftp_clone( + rtems_filesystem_location_info_t *loc +) +{ + int rv = 0; + + loc->node_access = strdup (loc->node_access); + + if (loc->node_access == NULL) { + errno = ENOMEM; + rv = -1; + } + + return rv; +} + +static void rtems_tftp_free_node_info( + const rtems_filesystem_location_info_t *loc +) +{ + free (loc->node_access); +} + +static bool rtems_tftp_are_nodes_equal( + const rtems_filesystem_location_info_t *a, + const rtems_filesystem_location_info_t *b +) +{ + return strcmp (a->node_access, b->node_access) == 0; +} + +static const rtems_filesystem_operations_table rtems_tftp_ops = { + .lock_h = rtems_filesystem_default_lock, + .unlock_h = rtems_filesystem_default_unlock, + .eval_path_h = rtems_tftp_eval_path, + .link_h = rtems_filesystem_default_link, + .are_nodes_equal_h = rtems_tftp_are_nodes_equal, + .mknod_h = rtems_filesystem_default_mknod, + .rmnod_h = rtems_filesystem_default_rmnod, + .fchmod_h = rtems_filesystem_default_fchmod, + .chown_h = rtems_filesystem_default_chown, + .clonenod_h = rtems_tftp_clone, + .freenod_h = rtems_tftp_free_node_info, + .mount_h = rtems_filesystem_default_mount, + .unmount_h = rtems_filesystem_default_unmount, + .fsunmount_me_h = rtems_tftpfs_shutdown, + .utime_h = rtems_filesystem_default_utime, + .symlink_h = rtems_filesystem_default_symlink, + .readlink_h = rtems_filesystem_default_readlink, + .rename_h = rtems_filesystem_default_rename, + .statvfs_h = rtems_filesystem_default_statvfs +}; + +static const rtems_filesystem_file_handlers_r rtems_tftp_handlers = { + .open_h = rtems_tftp_open, + .close_h = rtems_tftp_close, + .read_h = rtems_tftp_read, + .write_h = rtems_tftp_write, + .ioctl_h = rtems_filesystem_default_ioctl, + .lseek_h = rtems_filesystem_default_lseek, + .fstat_h = rtems_tftp_fstat, + .ftruncate_h = rtems_tftp_ftruncate, + .fsync_h = rtems_filesystem_default_fsync_or_fdatasync, + .fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync, + .fcntl_h = rtems_filesystem_default_fcntl, + .kqfilter_h = rtems_filesystem_default_kqfilter, + .mmap_h = rtems_filesystem_default_mmap, + .poll_h = rtems_filesystem_default_poll, + .readv_h = rtems_filesystem_default_readv, + .writev_h = rtems_filesystem_default_writev +}; -- cgit v1.2.3