From mboxrd@z Thu Jan 1 00:00:00 1970 Return-path: Received: from 18.mo5.mail-out.ovh.net ([178.33.45.10] helo=mo5.mail-out.ovh.net) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1RzLO5-0002Xr-WF for barebox@lists.infradead.org; Mon, 20 Feb 2012 05:02:12 +0000 Received: from mail98.ha.ovh.net (b6.ovh.net [213.186.33.56]) by mo5.mail-out.ovh.net (Postfix) with SMTP id 12203FF9A51 for ; Mon, 20 Feb 2012 06:04:39 +0100 (CET) Date: Mon, 20 Feb 2012 05:54:37 +0100 From: Jean-Christophe PLAGNIOL-VILLARD Message-ID: <20120220045437.GC27599@game.jcrosoft.org> References: <1329674288-25583-1-git-send-email-s.hauer@pengutronix.de> <1329674288-25583-2-git-send-email-s.hauer@pengutronix.de> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <1329674288-25583-2-git-send-email-s.hauer@pengutronix.de> List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: barebox-bounces@lists.infradead.org Errors-To: barebox-bounces+u.kleine-koenig=pengutronix.de@lists.infradead.org Subject: Re: [PATCH 1/5] Add suport for tftp as a filesystem To: Sascha Hauer Cc: barebox@lists.infradead.org On 18:58 Sun 19 Feb , Sascha Hauer wrote: > This adds tftp filesystem support. It currently duplicates > significant amounts of the tftp (command) support. This is ok > since we can eventually drop the original tftp command later. > > tftp is not really suitable to be handled as a filesystem. It lacks > support for stat, reading directories and other things. Handling > it as a filesystem has one big advantage though: tftp is no special > case for boot scripts and/or commands anymore which makes them simpler. > > This implementation has some improvements to the original tftp command. > It supports blocksize negotiation which speeds up transfers if the tftp > server supports it. Also we can determine the filesize to transfer if > the remote end supports it. > look good do you have seach patch for nfs? Best Regards, J. > Signed-off-by: Sascha Hauer > --- > fs/Kconfig | 4 + > fs/Makefile | 1 + > fs/tftp.c | 644 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 649 insertions(+), 0 deletions(-) > create mode 100644 fs/tftp.c > > diff --git a/fs/Kconfig b/fs/Kconfig > index 56c02da..6208cd2 100644 > --- a/fs/Kconfig > +++ b/fs/Kconfig > @@ -16,6 +16,10 @@ config FS_DEVFS > default y > prompt "devfs support" > > +config FS_TFTP > + bool > + prompt "tftp support" > + > source fs/fat/Kconfig > > config PARTITION_NEED_MTD > diff --git a/fs/Makefile b/fs/Makefile > index 7cae2b6..d204093 100644 > --- a/fs/Makefile > +++ b/fs/Makefile > @@ -4,3 +4,4 @@ obj-y += devfs-core.o > obj-$(CONFIG_FS_DEVFS) += devfs.o > obj-$(CONFIG_FS_FAT) += fat/ > obj-y += fs.o > +obj-$(CONFIG_FS_TFTP) += tftp.o > diff --git a/fs/tftp.c b/fs/tftp.c > new file mode 100644 > index 0000000..512da03 > --- /dev/null > +++ b/fs/tftp.c > @@ -0,0 +1,644 @@ > +/* > + * tftp.c > + * > + * Copyright (c) 2011 Sascha Hauer , Pengutronix > + * > + * See file CREDITS for list of people who contributed to this > + * project. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 > + * as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#define TFTP_PORT 69 /* Well known TFTP port # */ > +#define TIMEOUT 5 /* Seconds to timeout for a lost pkt */ > + > +/* After this time without a response from the server we will resend a packet */ > +#define TFTP_RESEND_TIMEOUT SECOND > + > +/* After this time without progress we will bail out */ > +#define TFTP_TIMEOUT (5 * SECOND) > + > +/* > + * TFTP operations. > + */ > +#define TFTP_RRQ 1 > +#define TFTP_WRQ 2 > +#define TFTP_DATA 3 > +#define TFTP_ACK 4 > +#define TFTP_ERROR 5 > +#define TFTP_OACK 6 > + > +#define STATE_RRQ 1 > +#define STATE_WRQ 2 > +#define STATE_RDATA 3 > +#define STATE_WDATA 4 > +#define STATE_OACK 5 > +#define STATE_WAITACK 6 > +#define STATE_LAST 7 > +#define STATE_DONE 8 > + > +#define TFTP_BLOCK_SIZE 512 /* default TFTP block size */ > + > +#define TFTP_ERR_RESEND 1 > + > +struct file_priv { > + struct net_connection *tftp_con; > + int push; > + uint16_t block; > + uint16_t last_block; > + int state; > + int err; > + int server_port; > + const char *filename; > + int filesize; > + uint64_t resend_timeout; > + uint64_t progress_timeout; > + struct kfifo *fifo; > + void *buf; > + int blocksize; > +}; > + > +struct tftp_priv { > + IPaddr_t server; > +}; > + > +static int tftp_create(struct device_d *dev, const char *pathname, mode_t mode) > +{ > + return 0; > +} > + > +static int tftp_unlink(struct device_d *dev, const char *pathname) > +{ > + return -ENOSYS; > +} > + > +static int tftp_mkdir(struct device_d *dev, const char *pathname) > +{ > + return -ENOSYS; > +} > + > +static int tftp_rmdir(struct device_d *dev, const char *pathname) > +{ > + return -ENOSYS; > +} > + > +static int tftp_truncate(struct device_d *dev, FILE *f, ulong size) > +{ > + return -ENOSYS; > +} > + > +static int tftp_send(struct file_priv *priv) > +{ > + unsigned char *xp; > + int len = 0; > + uint16_t *s; > + unsigned char *pkt = net_udp_get_payload(priv->tftp_con); > + int ret; > + > + debug("%s: state %d\n", __func__, priv->state); > + > + switch (priv->state) { > + case STATE_RRQ: > + case STATE_WRQ: > + xp = pkt; > + s = (uint16_t *)pkt; > + if (priv->state == STATE_RRQ) > + *s++ = htons(TFTP_RRQ); > + else > + *s++ = htons(TFTP_WRQ); > + pkt = (unsigned char *)s; > + pkt += sprintf((unsigned char *)pkt, > + "%s%c" > + "octet%c" > + "timeout%c" > + "%d%c" > + "tsize%c" > + "%d%c" > + "blksize%c" > + "1432", > + priv->filename, 0, > + 0, > + 0, > + TIMEOUT, 0, > + 0, > + priv->filesize, 0, > + 0); > + pkt++; > + len = pkt - xp; > + break; > + > + case STATE_RDATA: > + case STATE_OACK: > + xp = pkt; > + s = (uint16_t *)pkt; > + *s++ = htons(TFTP_ACK); > + *s++ = htons(priv->block); > + pkt = (unsigned char *)s; > + len = pkt - xp; > + break; > + } > + > + ret = net_udp_send(priv->tftp_con, len); > + > + return ret; > +} > + > +static int tftp_send_write(struct file_priv *priv, void *buf, int len) > +{ > + uint16_t *s; > + unsigned char *pkt = net_udp_get_payload(priv->tftp_con); > + int ret; > + > + s = (uint16_t *)pkt; > + *s++ = htons(TFTP_DATA); > + *s++ = htons(priv->block); > + memcpy((void *)s, buf, len); > + if (len < priv->blocksize) > + priv->state = STATE_LAST; > + len += 4; > + > + ret = net_udp_send(priv->tftp_con, len); > + priv->last_block = priv->block; > + priv->state = STATE_WAITACK; > + > + return ret; > +} > + > +static int tftp_poll(struct file_priv *priv) > +{ > + if (ctrlc()) { > + priv->state = STATE_DONE; > + priv->err = -EINTR; > + return -EINTR; > + } > + > + if (is_timeout(priv->resend_timeout, TFTP_RESEND_TIMEOUT)) { > + printf("T "); > + priv->resend_timeout = get_time_ns(); > + return TFTP_ERR_RESEND; > + } > + > + if (is_timeout(priv->progress_timeout, TFTP_TIMEOUT)) { > + priv->state = STATE_DONE; > + priv->err = -ETIMEDOUT; > + return -ETIMEDOUT; > + } > + > + net_poll(); > + > + return 0; > +} > + > +static void tftp_parse_oack(struct file_priv *priv, unsigned char *pkt, int len) > +{ > + unsigned char *opt, *val, *s; > + > + pkt[len - 1] = 0; > + > + debug("got OACK\n"); > +#ifdef DEBUG > + memory_display(pkt, 0, len, 1); > +#endif > + > + s = pkt; > + > + while (s < pkt + len) { > + opt = s; > + val = s + strlen(s) + 1; > + if (val > s + len) > + return; > + if (!strcmp(opt, "tsize")) > + priv->filesize = simple_strtoul(val, NULL, 10); > + if (!strcmp(opt, "blksize")) > + priv->blocksize = simple_strtoul(val, NULL, 10); > + debug("OACK opt: %s val: %s\n", opt, val); > + s = val + strlen(val) + 1; > + } > +} > + > +static void tftp_handler(void *ctx, char *packet, unsigned len) > +{ > + struct file_priv *priv = ctx; > + uint16_t proto; > + uint16_t *s; > + char *pkt = net_eth_to_udp_payload(packet); > + struct udphdr *udp = net_eth_to_udphdr(packet); > + > + len = net_eth_to_udplen(packet); > + if (len < 2) > + return; > + > + len -= 2; > + > + s = (uint16_t *)pkt; > + proto = *s++; > + pkt = (unsigned char *)s; > + > + debug("%s: proto 0x%04x\n", __func__, proto); > + > + switch (ntohs(proto)) { > + case TFTP_RRQ: > + case TFTP_WRQ: > + default: > + break; > + case TFTP_ACK: > + if (!priv->push) > + break; > + > + priv->block = ntohs(*(uint16_t *)pkt); > + if (priv->block != priv->last_block) { > + debug("ack %d != %d\n", priv->block, priv->last_block); > + break; > + } > + > + priv->block++; > + if (priv->state == STATE_LAST) { > + priv->state = STATE_DONE; > + break; > + } > + priv->tftp_con->udp->uh_dport = udp->uh_sport; > + priv->state = STATE_WDATA; > + break; > + > + case TFTP_OACK: > + tftp_parse_oack(priv, pkt, len); > + priv->server_port = ntohs(udp->uh_sport); > + priv->tftp_con->udp->uh_dport = udp->uh_sport; > + > + if (priv->push) { > + /* send first block */ > + priv->state = STATE_WDATA; > + priv->block = 1; > + } else { > + /* send ACK */ > + priv->state = STATE_OACK; > + priv->block = 0; > + tftp_send(priv); > + } > + > + break; > + case TFTP_DATA: > + if (len < 2) > + return; > + len -= 2; > + priv->block = ntohs(*(uint16_t *)pkt); > + > + if (priv->state == STATE_RRQ || priv->state == STATE_OACK) { > + /* first block received */ > + priv->state = STATE_RDATA; > + priv->tftp_con->udp->uh_dport = udp->uh_sport; > + priv->server_port = ntohs(udp->uh_sport); > + priv->last_block = 0; > + > + if (priv->block != 1) { /* Assertion */ > + printf("error: First block is not block 1 (%d)\n", > + priv->block); > + priv->err = -EINVAL; > + priv->state = STATE_DONE; > + break; > + } > + } > + > + if (priv->block == priv->last_block) > + /* Same block again; ignore it. */ > + break; > + > + priv->last_block = priv->block; > + > + kfifo_put(priv->fifo, pkt + 2, len); > + > + if (len < priv->blocksize) { > + tftp_send(priv); > + priv->state = STATE_DONE; > + } > + > + break; > + > + case TFTP_ERROR: > + debug("\nTFTP error: '%s' (%d)\n", > + pkt + 2, ntohs(*(uint16_t *)pkt)); > + switch (ntohs(*(uint16_t *)pkt)) { > + case 1: > + priv->err = -ENOENT; > + break; > + case 2: > + priv->err = -EACCES; > + break; > + default: > + priv->err = -EINVAL; > + break; > + } > + priv->state = STATE_DONE; > + break; > + } > +} > + > +static void tftp_timer_reset(struct file_priv *priv) > +{ > + priv->progress_timeout = priv->resend_timeout = get_time_ns(); > +} > + > +static struct file_priv *tftp_do_open(struct device_d *dev, > + int accmode, const char *filename) > +{ > + struct file_priv *priv; > + struct tftp_priv *tpriv = dev->priv; > + int ret; > + > + priv = xzalloc(sizeof(*priv)); > + > + filename++; > + > + switch (accmode & O_ACCMODE) { > + case O_RDONLY: > + priv->push = 0; > + priv->state = STATE_RRQ; > + break; > + case O_WRONLY: > + priv->push = 1; > + priv->state = STATE_WRQ; > + break; > + case O_RDWR: > + ret = -ENOSYS; > + goto out; > + } > + > + priv->block = 1; > + priv->err = -EINVAL; > + priv->filename = filename; > + priv->blocksize = TFTP_BLOCK_SIZE; > + > + priv->fifo = kfifo_alloc(4096); > + if (!priv->fifo) { > + ret = -ENOMEM; > + goto out; > + } > + > + priv->tftp_con = net_udp_new(tpriv->server, TFTP_PORT, tftp_handler, > + priv); > + if (IS_ERR(priv->tftp_con)) { > + ret = PTR_ERR(priv->tftp_con); > + goto out1; > + } > + > + ret = tftp_send(priv); > + if (ret) > + goto out2; > + > + tftp_timer_reset(priv); > + while (priv->state != STATE_RDATA && > + priv->state != STATE_DONE && > + priv->state != STATE_WDATA) { > + ret = tftp_poll(priv); > + if (ret == TFTP_ERR_RESEND) > + tftp_send(priv); > + if (ret < 0) > + goto out2; > + } > + > + if (priv->state == STATE_DONE) { > + ret = priv->err; > + goto out2; > + } > + > + priv->buf = xmalloc(priv->blocksize); > + > + return priv; > +out2: > + net_unregister(priv->tftp_con); > +out1: > + kfifo_free(priv->fifo); > +out: > + free(priv); > + > + return ERR_PTR(ret); > +} > + > +static int tftp_open(struct device_d *dev, FILE *file, const char *filename) > +{ > + struct file_priv *priv; > + > + priv = tftp_do_open(dev, file->flags, filename); > + if (IS_ERR(priv)) > + return PTR_ERR(priv); > + > + file->inode = priv; > + file->size = SZ_2G; > + > + return 0; > +} > + > +static int tftp_do_close(struct file_priv *priv) > +{ > + int ret; > + > + if (priv->push && priv->state != STATE_DONE) { > + int len; > + > + len = kfifo_get(priv->fifo, priv->buf, priv->blocksize); > + tftp_send_write(priv, priv->buf, len); > + priv->state = STATE_LAST; > + > + tftp_timer_reset(priv); > + > + while (priv->state != STATE_DONE) { > + ret = tftp_poll(priv); > + if (ret == TFTP_ERR_RESEND) > + tftp_send_write(priv, priv->buf, len); > + if (ret < 0) > + break; > + } > + } > + > + if (!priv->push && priv->state != STATE_DONE) { > + uint16_t *pkt = net_udp_get_payload(priv->tftp_con); > + *pkt++ = htons(TFTP_ERROR); > + *pkt++ = 0; > + *pkt++ = 0; > + net_udp_send(priv->tftp_con, 6); > + } > + > + net_unregister(priv->tftp_con); > + kfifo_free(priv->fifo); > + free(priv->buf); > + free(priv); > + > + return 0; > +} > + > +static int tftp_close(struct device_d *dev, FILE *f) > +{ > + struct file_priv *priv = f->inode; > + > + return tftp_do_close(priv); > +} > + > +static int tftp_write(struct device_d *_dev, FILE *f, const void *inbuf, > + size_t insize) > +{ > + struct file_priv *priv = f->inode; > + size_t size, now; > + int ret; > + > + debug("%s: %d\n", __func__, insize); > + > + size = insize; > + > + while (size) { > + now = kfifo_put(priv->fifo, inbuf, size); > + > + while (kfifo_len(priv->fifo) >= priv->blocksize) { > + kfifo_get(priv->fifo, priv->buf, priv->blocksize); > + > + tftp_send_write(priv, priv->buf, priv->blocksize); > + tftp_timer_reset(priv); > + > + while (priv->state == STATE_WAITACK) { > + ret = tftp_poll(priv); > + if (ret == TFTP_ERR_RESEND) > + tftp_send_write(priv, priv->buf, > + priv->blocksize); > + if (ret < 0) > + return ret; > + } > + } > + size -= now; > + inbuf += now; > + } > + > + return insize; > +} > + > +static int tftp_read(struct device_d *dev, FILE *f, void *buf, size_t insize) > +{ > + struct file_priv *priv = f->inode; > + size_t outsize = 0, now; > + int ret; > + > + debug("%s %d\n", __func__, insize); > + > + tftp_timer_reset(priv); > + > + while (insize) { > + now = kfifo_get(priv->fifo, buf, insize); > + if (priv->state == STATE_DONE) > + return outsize + now; > + if (now) { > + outsize += now; > + buf += now; > + insize -= now; > + tftp_send(priv); > + tftp_timer_reset(priv); > + } > + > + ret = tftp_poll(priv); > + if (ret == TFTP_ERR_RESEND) > + tftp_send(priv); > + if (ret < 0) > + return ret; > + } > + > + return outsize; > +} > + > +static off_t tftp_lseek(struct device_d *dev, FILE *f, off_t pos) > +{ > + /* not implemented in tftp protocol */ > + return -ENOSYS; > +} > + > +static DIR* tftp_opendir(struct device_d *dev, const char *pathname) > +{ > + /* not implemented in tftp protocol */ > + return NULL; > +} > + > +static int tftp_stat(struct device_d *dev, const char *filename, struct stat *s) > +{ > + struct file_priv *priv; > + > + priv = tftp_do_open(dev, O_RDONLY, filename); > + if (IS_ERR(priv)) > + return PTR_ERR(priv); > + > + s->st_mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; > + s->st_size = priv->filesize; > + > + tftp_do_close(priv); > + > + return 0; > +} > + > +static int tftp_probe(struct device_d *dev) > +{ > + struct fs_device_d *fsdev = dev->type_data; > + struct tftp_priv *priv = xzalloc(sizeof(struct tftp_priv)); > + > + dev->priv = priv; > + > + string_to_ip(fsdev->backingstore, &priv->server); > + > + return 0; > +} > + > +static void tftp_remove(struct device_d *dev) > +{ > + struct tftp_priv *priv = dev->priv; > + > + free(priv); > +} > + > +static struct fs_driver_d tftp_driver = { > + .open = tftp_open, > + .close = tftp_close, > + .read = tftp_read, > + .lseek = tftp_lseek, > + .opendir = tftp_opendir, > + .stat = tftp_stat, > + .create = tftp_create, > + .unlink = tftp_unlink, > + .mkdir = tftp_mkdir, > + .rmdir = tftp_rmdir, > + .write = tftp_write, > + .truncate = tftp_truncate, > + .flags = 0, > + .drv = { > + .probe = tftp_probe, > + .remove = tftp_remove, > + .name = "tftp", > + .type_data = &tftp_driver, > + } > +}; > + > +static int tftp_init(void) > +{ > + return register_fs_driver(&tftp_driver); > +} > +coredevice_initcall(tftp_init); > -- > 1.7.9 > > > _______________________________________________ > barebox mailing list > barebox@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/barebox _______________________________________________ barebox mailing list barebox@lists.infradead.org http://lists.infradead.org/mailman/listinfo/barebox