[PATCH 1/5] Add suport for tftp as a filesystem
Jean-Christophe PLAGNIOL-VILLARD
plagnioj at jcrosoft.com
Sun Feb 19 23:54:37 EST 2012
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 <s.hauer at pengutronix.de>
> ---
> 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 <s.hauer at pengutronix.de>, 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 <common.h>
> +#include <command.h>
> +#include <net.h>
> +#include <driver.h>
> +#include <clock.h>
> +#include <fs.h>
> +#include <errno.h>
> +#include <libgen.h>
> +#include <fcntl.h>
> +#include <getopt.h>
> +#include <fs.h>
> +#include <init.h>
> +#include <linux/stat.h>
> +#include <linux/err.h>
> +#include <kfifo.h>
> +#include <sizes.h>
> +
> +#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 at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/barebox
More information about the barebox
mailing list