Minor fixes to sysconfsetup (which failed on systems using
authorJohn Bowler <jbowler@nslu2-linux.org>
Wed, 25 May 2005 06:59:10 +0000 (06:59 +0000)
committerJohn Bowler <jbowler@nslu2-linux.org>
Wed, 25 May 2005 06:59:10 +0000 (06:59 +0000)
udev for /dev) and devio (which failed on write if it had
to write more than one buffer.)

BKrev: 4294223e3XWI1bkqAgS1GviX6o-TAw

packages/openslug-init/openslug-init-0.10/devio.c
packages/openslug-init/openslug-init-0.10/sysconfsetup
packages/openslug-init/openslug-init_0.10.bb

index e69de29..bfaace2 100644 (file)
+/* vi: set sw=4 ts=4: */
+/*
+ * devio: correctly read a region of a device
+ *
+ * A dd like program designed to read correctly from mtd character
+ * (and maybe block) devices.  Allows access to specific regions
+ * of the device and allows output of numbers from specific locations.
+ *
+ * Copyright (C) 2005 John Bowler <jbowler@acm.org>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifndef S_ISSOCK
+#define S_ISSOCK(fd) 0
+#endif
+
+/* Define to 0 to remove the detailed help. */
+#ifndef HELP
+#define HELP 1
+#endif
+
+#ifndef STR_MAX
+#define STR_MAX 4096
+#endif
+
+/* common error-and-die functions - reduces code size slightly. */
+static int error_level = 1; /* Increased by write operations. */
+
+/* NDEBUG will only save about 600 bytes! */
+/* The noreturn attribute helps reduce size by 300 bytes, and removes
+ * warning messages
+ */
+#if NDEBUG
+static void do_die(const unsigned char *why) __attribute__((noreturn));
+static void do_die(const unsigned char *why) {
+       fprintf(stderr, "devio: %s\n", why);
+       exit(error_level);
+}
+#define die(a,b) do_die(a)
+#else
+static void do_die(const unsigned char *why, const unsigned char *infile)
+    __attribute__((noreturn));
+static void do_die(const unsigned char *why, const unsigned char *infile) {
+       fprintf(stderr, "devio: %s: %s\n", infile, why);
+       exit(error_level);
+}
+#define die(a,b) do_die((a),(b))
+#endif
+
+#if NDEBUG
+static void do_pdie(const unsigned char *why) __attribute__((noreturn));
+static void do_pdie(const unsigned char *why) {
+       fprintf(stderr, "devio: %s: %s\n", why, strerror(errno));
+       exit(error_level);
+}
+#define pdie(a,b) do_pdie(a)
+#else
+static void do_pdie(const unsigned char *why, const unsigned char *infile)
+    __attribute__((noreturn));
+static void do_pdie(const unsigned char *why, const unsigned char *infile) {
+       fprintf(stderr, "devio: %s: %s: %s\n", infile, why, strerror(errno));
+       exit(error_level);
+}
+#define pdie(a,b) do_pdie((a),(b))
+#endif
+
+/* This is a non-standard assert but it saves quite a lot of
+ * space (1kbyte) over the OS version.
+ */
+#if NDEBUG
+#define assert(condition) do;while(0)
+#elif 0 /* Expensive string asserts (lots of space in strings). */
+static void do_assert(const unsigned char *why)
+    __attribute__((noreturn));
+static void do_assert(const unsigned char *why) {
+       fprintf(stderr, "devio: internal error: %s\n", why);
+       exit(error_level);
+}
+#define assert(condition) do if (!(condition)) do_assert(#condition); while (0)
+#else
+static void do_assert(int line) __attribute__((noreturn));
+static void do_assert(int line) {
+       fprintf(stderr, "devio: internal error: %d\n", line);
+       exit(error_level);
+}
+#define assert(condition) do if (!(condition)) do_assert(__LINE__); while (0)
+#endif
+
+/* This is a non-ANSI extension. */
+unsigned char *my_strdup(const unsigned char *from) {
+       size_t cb = strlen(from)+1;
+       unsigned char *to = malloc(cb);
+       if (to == 0)
+               die("out of memory", from);
+       memcpy(to, from, cb);
+       return to;
+}
+
+
+/***********************************************************************
+ * mtd_file
+ *
+ * Basic device safe IO.
+ * Set mtd_seek to set the desired read or write point.
+ * Use mtd_getb and mtd_putb to read/write a single byte.
+ * Use mtd_readbytes and mtd_writebytes to move multiple bytes.
+ *********************************************************************/
+/* File structure, used for read and write operations.  stdio() should do
+ * everything this does pretty much except that this allows for no-write
+ * buffering and it is 'weird' in that it won't overwrite beyond the end
+ * of the data. */
+typedef struct mtd_file {
+       unsigned char* pname;
+       int            fwrite;
+       int            fverify;  /* do not write, just verify */
+       int            fwritten; /* something to do! */
+       int            fchanged; /* something was done! */
+       int            fd;
+       struct stat    stat;
+       size_t         cbbuf;
+       /* The user pointer is at 'useroffset', the buffer contains data from
+        * 'bufferoffset' to 'deviceoffset' (exclusive - the buffer may be empty), so
+        * the file descriptor is pointing to 'deviceoffset', which may be just beyond
+        * the end of the file.
+        */
+       off_t          useroffset;   /* Current user position */
+       off_t          bufferoffset; /* Base of current buffer */
+       off_t          deviceoffset; /* End of current buffer */
+       off_t          endoffset;    /* Length of char or block device. */
+       unsigned char* pbuf;
+       unsigned char* pwritebuf;
+} mtd_file;
+
+
+/* Initialise an mtd structure. */
+static void init_mtd(mtd_file *pfile) {
+       pfile->pname = 0;
+       pfile->fwrite = 0;
+       pfile->fverify = 0;
+       pfile->fwritten = 0;
+       pfile->fchanged = 0;
+       pfile->fd = (-1);
+       memset(&pfile->stat, 0, sizeof pfile->stat);
+       pfile->cbbuf = 0;
+       pfile->useroffset = 0;
+       pfile->bufferoffset  = 0;
+       pfile->deviceoffset = 0;
+       pfile->endoffset = (off_t)-1;
+       pfile->pbuf = 0;
+       pfile->pwritebuf = 0;
+}
+
+
+/* Return the size, in bytes. */
+static size_t size_mtd(mtd_file *pfile) {
+       if (S_ISCHR(pfile->stat.st_mode) || S_ISBLK(pfile->stat.st_mode)) {
+               if (pfile->endoffset == (off_t)-1) {
+                       off_t len;
+                       assert(pfile->stat.st_size == 0);
+                       assert(pfile->stat.st_blocks == 0);
+                       assert(pfile->stat.st_blksize > 0);
+                       /* So seek to the end then come back here. */
+                       len = lseek(pfile->fd, -pfile->stat.st_blksize, SEEK_END);
+                       if (len == (off_t)-1)
+                               pdie("lseek(length)", pfile->pname);
+                       if (lseek(pfile->fd, pfile->deviceoffset, SEEK_SET) != pfile->deviceoffset)
+                               pdie("lseek(length reset)", pfile->pname);
+                       len += pfile->stat.st_blksize;
+                       pfile->endoffset = len;
+               }
+               return pfile->endoffset;
+       } else if (S_ISDIR(pfile->stat.st_mode) || S_ISFIFO(pfile->stat.st_mode) ||
+                               S_ISSOCK(pfile->stat.st_mode))
+               die("cannot find size of this device", pfile->pname);
+       else
+               return pfile->stat.st_size;
+}
+
+
+/* Open the named file for read or write, the structure is initialised
+ * appropriately.  The name is copied. */
+static void new_mtd(mtd_file *pfile, const char *pname, int fwrite, int fverify, int fd) {
+       pfile->pname = my_strdup(pname);
+       pfile->fwrite = fwrite;
+       pfile->fverify = fverify;
+       pfile->fwritten = 0;
+       pfile->fchanged = 0;
+       pfile->fd = fd;
+
+       if (fstat(fd, &pfile->stat) != 0)
+               pdie("fstat", pname);
+       /* This can be made to work with fifos on read because it is possible
+        * to seek by reading so long as we only seek forward, but it really
+        * isn't worth spending time on this.
+        */
+       if (S_ISDIR(pfile->stat.st_mode) || S_ISFIFO(pfile->stat.st_mode) ||
+                       S_ISSOCK(pfile->stat.st_mode))
+               die("invalid device", pname);
+       /* Allow writing to a file for testing - i.e. S_ISREG is fine above. */
+       pfile->cbbuf = pfile->stat.st_blksize;
+       if (pfile->cbbuf == 0)
+               pfile->cbbuf = 4096;
+       pfile->useroffset = 0;
+       pfile->bufferoffset = 0;
+       pfile->deviceoffset = 0;
+       pfile->pbuf = 0;
+       pfile->pwritebuf = 0;
+}
+
+static void open_mtd(mtd_file *pfile, const char *pname, int fwrite, int fverify) {
+       int fd = open(pname, (fwrite && !fverify) ? O_RDWR : O_RDONLY);
+       if (fd < 0)
+               pdie("open", pname);
+       else if (fd < 3)
+               die("no standard streams", "-");
+       new_mtd(pfile, pname, fwrite, fverify, fd);
+}
+
+
+/* Do the actual write.  Any pending write buffers are checked and output
+ * to the device.  Happens on close and can be called before.  Does not
+ * do an fsync.  The fwritten flag indicates that write_mtd needs to be
+ * called, the fchanegd flag indicates that something has been written and
+ * an fdatasync needs to happen before the close.
+ */
+static void write_mtd(mtd_file *pfile) {
+       if (pfile->fwritten) {
+               size_t count = pfile->deviceoffset - pfile->bufferoffset;
+               unsigned char *pbuf = pfile->pwritebuf;
+
+               assert(pfile->pbuf != 0);
+               assert(pbuf != 0);
+               assert(pfile->deviceoffset > pfile->bufferoffset);
+               assert(pfile->deviceoffset <= pfile->bufferoffset + pfile->cbbuf);
+               /* If it changed write it. */
+               if (memcmp(pfile->pbuf, pbuf, count) != 0) {
+                       /* If verifying the verify just failed... */
+                       if (pfile->fverify)
+                               die("verification failed", pfile->pname);
+
+                       /* So write the whole of this buffer back.  Do not do a sync here
+                        * because that would force a complete write of the flash erase
+                        * block - not good.
+                        */
+                       if (lseek(pfile->fd, pfile->bufferoffset, SEEK_SET) != pfile->bufferoffset)
+                               pdie("lseek(write)", pfile->pname);
+                       /* write, well, you have to keep doing it until it works, you
+                        * also have to RTFM several times to get this write, so if
+                        * this looks wrong please fix it.  No, not that, that was
+                        * deliberate.
+                        */
+                       do {
+                               ssize_t cb = write(pfile->fd, pbuf, count);
+                               assert(cb != 0);
+                               assert(cb <= count);
+                               if (cb < 0) switch (errno) {
+                               case EINTR:  /* shall we try that again then? */
+                                       /* This is the common case - this does happen, it is
+                                        * necessary to deal with it and it is sufficient to
+                                        * try again.
+                                        */
+                                       break;
+                               case EAGAIN: /* what, oh well, if at once you don't succeed. */
+                                       /* We didn't say O_NONBLOCK above so this should never
+                                        * happen, however it has.  The code will therefore go into
+                                        * a tight loop in the manner of a certain Scottish nobleman.
+                                        */
+                                       break;
+                               case EPIPE:  /* you don't love me any more. */
+                                       /* This is a little difficult, it means we were squirting
+                                        * data down a pipe, so somehow someone has managed to work
+                                        * out both how to create a named pipe and how much fun to
+                                        * have by passing it to this program on the command line,
+                                        * then they have worked out how to make the shell ignore
+                                        * SIGPIPE in a spawned program (possible with some shells)
+                                        * then they want to see the really dumb message that comes
+                                        * out as a result.  We just say no.
+                                        */
+                                       exit(1);
+                               default:
+                                       pdie("write", pfile->pname);
+                               } else {
+                                       count -= cb;
+                                       /* It is now necessary to fdatasync this file descriptor
+                                        * to ensure that this data really does get to its final
+                                        * destination.  (Note that even this is probably not certain
+                                        * if the destination is a disk with a RAM buffer - which
+                                        * means *any* disk these days.)
+                                        */
+                                       pfile->fchanged = 1;
+                                       /* Something has been written to flash, but not everything
+                                        * has (necessarily) been written yet, so if something goes
+                                        * wrong after this point we are in deep, deep, trouble.
+                                        */
+                                       error_level = 3;
+                               }
+                       } while (count > 0);
+
+                       /* So now the device matches the write buffer and the device
+                        * pointer is back where it was before.
+                        */
+                       memcpy(pfile->pbuf, pfile->pwritebuf, pfile->deviceoffset-pfile->bufferoffset);
+               }
+               /* Nothing remains to write from this buffer (hence nothing at all
+                * for this device.)
+                */
+               pfile->fwritten = 0;
+       }
+}
+
+
+/* Close the file, if anything was written out does an fsync.
+ */
+static void close_mtd(mtd_file *pfile) {
+       write_mtd(pfile);
+       assert(!pfile->fwritten);
+       if (pfile->pbuf != 0) {
+               free(pfile->pbuf);
+               pfile->pbuf = 0;
+       }
+       if (pfile->pwritebuf != 0) {
+               free(pfile->pwritebuf);
+               pfile->pwritebuf = 0;
+       }
+       if (pfile->fd >= 0) {
+               /* For a write file be very very careful.  For read ignore errors:
+                * it is more important to successfully write than to whine about
+                * strange close errors from a file we don't care about.  For a
+                * write file with nothing written we don't care either.
+                */
+               if (pfile->fchanged) {
+                       /* This is the all important bit.  Doing the fdatasync is what
+                        * flushes the data to the flash.  If this isn't done there is
+                        * no guarantee that close will detect a write error, 'cause the
+                        * flash may not have completed the write before the close
+                        * returns.
+                        */
+                       if (fdatasync(pfile->fd) != 0) {
+                               /* Trying an fdatasync on a pipe, etc, is silly, but we do
+                                * it anyway.  EROFS means we just tried to write to a
+                                * read only file system, safe but still an error.
+                                */
+                               if (errno != EINVAL)
+                                       pdie("sync", pfile->pname);
+                       }
+                       if (close(pfile->fd) != 0)
+                               pdie("close", pfile->pname);
+               } else
+                       (void)close(pfile->fd);
+               pfile->fd = (-1);
+       }
+       if (pfile->pname != 0) {
+               free(pfile->pname);
+               pfile->pname = 0;
+       }
+       init_mtd(pfile);
+}
+
+
+/* Obtain an input and, if necessary, an output buffer. */
+static void buffer_mtd(mtd_file *pfile) {
+       if (pfile->pbuf == 0) {
+               size_t blksize = pfile->cbbuf;
+               assert(blksize > 0);
+               assert(pfile->pwritebuf == 0);
+
+               /* Get blksize bytes (note: things could be speeded up by aligning
+                * the buffer but this really doesn't matter, all the time goes in
+                * read/write of the flash!)
+                */
+               pfile->pbuf = malloc(blksize);
+               if (pfile->fwrite)
+                       pfile->pwritebuf = malloc(blksize);
+               if (pfile->pbuf == 0 || (pfile->fwrite && pfile->pwritebuf == 0))
+                       die("out of memory", pfile->pname);
+       }
+}
+
+
+/* Read some data including the current user position.  This will also *write* data
+ * if something is waiting to be written.
+ *
+ * NOTE: in the original design I conceived of some scheme whereby all the writes
+ * would be buffered up for the end, but I can't see how this would actually help
+ * anything because even if data has to be read from the device to determine read
+ * locations it tends to happen before the relevant writes.  In the access patterns
+ * I know (they are very simple - and that is important in itself) there is never
+ * a need to read from a write device.
+ */
+static void read_mtd(mtd_file *pfile) {
+       size_t cbread;
+       int ioffset;
+
+       /* 'useroffset' is where we need to read from, 'deviceoffset' is where we are
+        * at (sic) and 'bufferoffset'..'deviceoffset' is what we have already.
+        */
+       if (pfile->useroffset >= pfile->bufferoffset &&
+               pfile->useroffset < pfile->deviceoffset)
+               return;
+
+       if (pfile->useroffset < 0 || pfile->useroffset >= size_mtd(pfile))
+               die("read outside file", pfile->pname);
+
+       /* Make sure there is a buffer. */
+       buffer_mtd(pfile);
+
+       /* This is the maximum amount which can be read. */
+       cbread = pfile->cbbuf;
+       if (pfile->useroffset >= pfile->bufferoffset &&
+               pfile->useroffset < pfile->bufferoffset + cbread) {
+               /* Just fill the rest of the buffer. */
+               ioffset = pfile->deviceoffset - pfile->bufferoffset;
+
+               assert(pfile->deviceoffset < pfile->bufferoffset + cbread);
+               cbread -= ioffset;
+       } else {
+               off_t base;
+
+               /* We to move the buffer therefore  any pending write needs to be flushed. */
+               write_mtd(pfile);
+               assert(!pfile->fwritten);
+
+               /* Seek to the aligned buffer boundary if necessary. */
+               base = (pfile->useroffset / cbread) * cbread;
+               if (base != pfile->deviceoffset) {
+                       if (lseek(pfile->fd, base, SEEK_SET) != base)
+                               pdie("lseek(read)", pfile->pname);
+                       pfile->deviceoffset = base;
+               }
+               pfile->bufferoffset = base;
+               ioffset = 0;
+       }
+
+       /* Reading is like writing, EINTR can stop it succeeding but is a
+        * continuable error.
+        */
+       assert(pfile->bufferoffset <= pfile->useroffset);
+       assert(pfile->useroffset < pfile->bufferoffset + cbread);
+       assert(pfile->deviceoffset <= pfile->useroffset);
+       do {
+               ssize_t cb = read(pfile->fd, pfile->pbuf+ioffset, cbread);
+               if (cb < 0) switch (errno) {
+               case EINTR:  /* simple restart */
+                       /* POSIX allows this to happen when something has been
+                        * read.  Reset the file pointer just in case.
+                        */
+                       if (lseek(pfile->fd, pfile->deviceoffset, SEEK_SET) != pfile->deviceoffset)
+                               pdie("lseek(read reset)", pfile->pname);
+                       break;
+               case EAGAIN: /* O_NONBLOCK on the input? */
+                       break;
+               default:
+                       pdie("read", pfile->pname);
+               } else if (cb == 0) {
+                       die("unexpected end of file", pfile->pname);
+               } else {
+                       /* Save a copy of the data so that it can be written out again
+                        * by a write file.
+                        */
+                       if (pfile->pwritebuf != 0)
+                               memcpy(pfile->pwritebuf+ioffset, pfile->pbuf+ioffset, cb);
+                       cbread -= cb;
+                       ioffset += cb;
+                       pfile->deviceoffset += cb;
+               }
+       } while (cbread > 0 && pfile->useroffset >= pfile->deviceoffset);
+
+       assert(pfile->useroffset < pfile->deviceoffset);
+}
+
+
+/* Basic IO - these are the functions to use, not the internal read/write
+ * functions above.
+ */
+/* Set the current read/write pointer on this file. */
+#if 0 /*UNUSED*/
+static void mtd_seek(mtd_file *pfile, off_t offset) {
+       pfile->useroffset = offset;
+}
+#endif
+
+
+/* Get a single byte (returned) and advance the read pointer by one. */
+static unsigned char mtd_getb(mtd_file *pfile) {
+       read_mtd(pfile);
+       return (pfile->fwrite ? pfile->pwritebuf : pfile->pbuf)[
+               pfile->useroffset++ - pfile->bufferoffset];
+}
+
+
+/* Store a single byte in a write file and advance the pointer by one. */
+static void mtd_putb(mtd_file *pfile, unsigned long b) {
+       if (!pfile->fwrite)
+               die("file is not writeable", pfile->pname);
+       read_mtd(pfile);
+       if (b != pfile->pwritebuf[pfile->useroffset-pfile->bufferoffset]) {
+               pfile->pwritebuf[pfile->useroffset-pfile->bufferoffset] = b;
+               pfile->fwritten = 1;
+       }
+       ++(pfile->useroffset);
+}
+
+
+/* Read a given number of bytes, which must exist in the file, and
+ * advance the pointer by that amount.
+ */
+static void mtd_readbytes(mtd_file *pfile, unsigned char *pbuf, size_t cb) {
+       if (pfile->useroffset+cb > size_mtd(pfile))
+               die("read beyond end of file", pfile->pname);
+
+       while (cb > 0) {
+               int cbavail;
+
+               read_mtd(pfile);
+               cbavail = pfile->deviceoffset - pfile->useroffset;
+               assert(cbavail > 0 && cbavail <= pfile->cbbuf);
+               if (cbavail > cb)
+                       cbavail = cb;
+
+               assert(pfile->useroffset >= pfile->bufferoffset);
+               assert(pfile->useroffset < pfile->deviceoffset);
+               assert(pfile->deviceoffset <= pfile->bufferoffset + pfile->cbbuf);
+
+               memcpy(pbuf, (pfile->fwrite ? pfile->pwritebuf : pfile->pbuf) +
+                                       (pfile->useroffset-pfile->bufferoffset), cbavail);
+               pfile->useroffset += cbavail;
+               pbuf += cbavail;
+               cb -= cbavail;
+       }
+}
+
+
+/* Write a given number of bytes and advance the pointer.  As with readbytes
+ * the bytes must already exist in the file - mtd_file will never extend the
+ * file only change existing bytes.
+ */
+static void mtd_writebytes(mtd_file *pfile, const unsigned char *pbuf, size_t cb) {
+       if (!pfile->fwrite)
+               die("file is not writeable", pfile->pname);
+       if (pfile->useroffset+cb > size_mtd(pfile))
+               die("write beyond end of file", pfile->pname);
+       while (cb > 0) {
+               int cbavail;
+
+               /* This may look strange but it is correct - this code always reads
+                * before it writes to avoid unnecessary writes.
+                */
+               read_mtd(pfile);
+               cbavail = pfile->deviceoffset - pfile->useroffset;
+               if (cbavail > cb)
+                       cbavail = cb;
+               memcpy(pfile->pwritebuf + (pfile->useroffset-pfile->bufferoffset), pbuf, cbavail);
+               pfile->fwritten = 1;
+               pfile->useroffset += cbavail;
+               pbuf += cbavail;
+               cb -= cbavail;
+       }
+}
+
+
+#if 0 /* Commented out because I don't think this is worth while. */
+/* Copy bytes from the pointer in one file to the pointer in another
+ * file (avoids an intermediate buffer compared to readbytes/writebytes.)
+ */
+static void mtd_copy(mtd_file *pto, mtd_file *pfrom, size_t cb) {
+       int cbfrom, cbto;
+
+       if (!pto->fwrite)
+               die("file is not writeable", pto->pname);
+       if (pto->useroffset+cb > size_mtd(pto))
+               die("write beyond end of file", pto->pname);
+       if (pfrom->useroffset+cb > size_mtd(pfrom))
+               die("read beyond end of file", pfrom->pname);
+       /* Copying from and to the same place has no effect. */
+       if (pfrom == pto)
+               return;
+
+       cbfrom = cbto = 0;
+       while (cb > 0) {
+               int cbavail;
+
+               if (cbfrom <= 0) {
+                       read_mtd(pfrom);
+                       cbfrom = pfrom->deviceoffset - pfrom->useroffset;
+                compared to readbytes/writebytes}
+               if (cbto <= 0) {
+                       read_mtd(pto);
+                       cbto = pto->deviceoffset - pto->useroffset;
+               }
+
+               /* Take the smallest byte count and copy it. */
+               cbavail = cbfrom;
+               if (cbavail > cbto)
+                       cbavail = cbto;
+               if (cbavail > cb)
+                       cbavail = cb;
+
+               memcpy(pto->pwritebuf + (pto->useroffset-pto->bufferoffset),
+                               pfrom->pbuf + (pfrom->useroffset-pfrom->bufferoffset),
+                               cbavail);
+               pto->fwritten = 1;
+
+               pto->useroffset += cbavail;
+               cbto -= cbavail;
+               pfrom->useroffset += cbavail;
+               cbfrom -= cbavail;
+
+               cb -= cbavail;
+       }
+}
+#endif
+
+
+/***********************************************************************
+ * parse
+ *
+ * Parse a command line option or a single line.  See the help below
+ * for details...
+ ***********************************************************************/
+#define STACK_BASE 8
+#define STACK_SIZE 256
+#define NUM_FILES  16
+typedef struct parse_buf {
+       int           fverify; /* Just verifying, do no write. */
+       int           cstack;
+       int           fbreak;  /* Break in an expression. */
+       mtd_file*     pfrom;
+       mtd_file*     pto;
+
+       /* The buffers. */
+       unsigned long variables[256];
+       unsigned long stack[STACK_SIZE];
+       mtd_file      files[NUM_FILES];
+} parse_buf;
+
+
+/* Initialiser. */
+static void init_parse(parse_buf *pp, int fverify) {
+       int i;
+       memset(pp, 0, sizeof *pp);
+       pp->fverify = fverify;
+       pp->cstack = STACK_BASE;
+       pp->fbreak = 0;
+       pp->pfrom = 0;
+       pp->pto = 0;
+       for (i=0; i<NUM_FILES; ++i)
+               init_mtd(pp->files+i);
+}
+
+
+/* Terminator. */
+static void quit(parse_buf *pp, int exit_code) __attribute__((noreturn));
+static void quit(parse_buf *pp, int exit_code) {
+       int i;
+       /* Close all the files. */
+       for (i=0; i<NUM_FILES; ++i)
+               if (pp->files[i].pname != 0)
+                       close_mtd(pp->files+i);
+
+       /* And make sure the output worked too. */
+       if (fflush(stdout) == EOF || ferror(stdout) || fclose(stdout) == EOF)
+               pdie("output failed", "stdout");
+
+       exit(exit_code);
+}
+
+
+/* Input a single byte. */
+static unsigned char inb(parse_buf *pp) {
+       int b;
+       if (pp->pfrom == 0) {
+               b = getchar();
+               if (b == EOF)
+                       pdie("read error", "stdin");
+       } else {
+               b = mtd_getb(pp->pfrom);
+       }
+       return b;
+}
+
+
+/* Output a single byte. */
+static void outb(parse_buf *pp, unsigned long b) {
+       if (pp->pto == 0) {
+               if (putchar(b) == EOF)
+                       pdie("write error", "stdout");
+       } else {
+               mtd_putb(pp->pto, b);
+       }
+}
+
+
+/* Output these bytes. */
+static void outputbytes(parse_buf *pp, const char *pbuf, size_t cb) {
+       if (pp->pto == 0) {
+               if (fwrite(pbuf, cb, 1, stdout) != 1)
+                       pdie("write error", "stdout");
+       } else
+               mtd_writebytes(pp->pto, pbuf, cb);
+}
+
+/* Copy a stream of bytes. */
+static void copybytes(parse_buf *pp, size_t cb) {
+       while (cb > 0) {
+               size_t cbavail = cb;
+               unsigned char buf[1024];
+               if (cbavail > sizeof buf)
+                       cbavail = sizeof buf;
+               if (pp->pfrom == 0) {
+                       if (fread(buf, cbavail, 1, stdin) != 1)
+                               pdie("read error", "stdin");
+               } else
+                       mtd_readbytes(pp->pfrom, buf, cbavail);
+
+               outputbytes(pp, buf, cbavail);
+
+               cb -= cbavail;
+       }
+}
+
+
+/* Fill the output with a count of bytes of a given value. */
+static void fillbytes(parse_buf *pp, unsigned long val, size_t cb) {
+       unsigned char buf[1024];
+       memset(buf, val, sizeof buf);
+
+       while (cb > 0) {
+               size_t cbavail = cb;
+               if (cbavail > sizeof buf)
+                       cbavail = sizeof buf;
+
+               outputbytes(pp, buf, cbavail);
+
+               cb -= cbavail;
+       }
+}
+
+
+/* Push a single numeric value onto the stack. */
+static void push(parse_buf *pp, unsigned long num, const unsigned char *str) {
+       if (pp->cstack >= STACK_SIZE)
+               die("stack overflow", str);
+       pp->stack[pp->cstack++] = num;
+}
+
+
+/* Pop one or move variables. */
+static void pop(parse_buf *pp, int num, const unsigned char *str) {
+       if (pp->cstack < STACK_BASE+num)
+               die("stack underflow", str);
+       pp->cstack -= num;
+}
+
+/* Return (and pop) the top of stack. */
+static unsigned long top(parse_buf *pp, const unsigned char *str) {
+       if (pp->cstack <= STACK_BASE)
+               die("stack underflow", str);
+       return pp->stack[--(pp->cstack)];
+}
+
+
+/* Store the result of an operator. */
+static void op(parse_buf *pp, int numpop, unsigned long num,
+               const unsigned char *str) {
+       pop(pp, numpop, str);
+       push(pp, num, str);
+}
+
+
+/* Parse a single expression, which may be empty.  The conditional execution
+ * stuff is identical to that for a command except that (:?) are used instead
+ * of $($:$?$)
+ */
+static int parse_expression(parse_buf *pp, const unsigned char *line, int Ac, int AcEnd) {
+       int SP = 0, fnoexec = 0, test = 0;
+       int stack[16];
+
+       for (;Ac<AcEnd;++Ac) {
+               const unsigned char *lp = line+Ac;
+               unsigned char ch = *lp;
+               switch (ch) {
+                       /* Control flow.  These operators have to explicitly check
+                        * the fnoexec state because they manipulate it.
+                        */
+               case '(': /* if */
+                       if (SP >= 16)
+                               die("() stack overflow", lp);
+                       stack[SP++] = Ac;
+                       if (fnoexec) {
+                               fnoexec += 3;
+                       } else {
+                               fnoexec = top(pp, lp) == 0;
+                       }
+                       break;
+
+               case '[': /* test start */
+                       /* If fnoexec >= 3 the whole block is disabled. */
+                       if (fnoexec <= 2) {
+                               /* Valid only inside an () block and there should only
+                                * be one active at once.
+                                */
+                               if (test != 0 || SP <= 0)
+                                       goto badnest;
+                               /* Record the start of the test. */
+                               test = Ac+1;
+                               /* If the previous block executed record this. */
+                               if (fnoexec == 0)
+                                       fnoexec = 2;
+                       }
+                       break;
+
+               case ':': /* elif */
+                       /* fnoexec is 1 if nothing has executed in this block yet,
+                        * and if the block itself is executing, it is 2 if something
+                        * did execute, it is >2 for a non-executed block, including
+                        * one with a break.
+                        */
+                       if (fnoexec <= 2) {
+                               if (test == 0 || SP <= 0)
+                                       goto badnest;
+
+                               assert(fnoexec > 0);
+                               assert(!pp->fbreak);
+                               /* 1: nothing has executed yet.
+                                * 2: an if or elif has executed.
+                                */
+                               if (fnoexec == 1) {
+                                       /* Parse the test.  If this results in a break no
+                                        * condition is popped from the stack, otherwise
+                                        * the condition which the expression should push
+                                        * is popped.
+                                        */
+                                       (void)parse_expression(pp, line, test, Ac);
+                                       if (pp->fbreak) {
+                                               fnoexec = 3;
+                                               pp->fbreak = 0;
+                                       } else
+                                               fnoexec = top(pp, lp) == 0;
+                               }
+
+                               /* And the test has been consumed. */
+                               test = 0;
+                       }
+                       break;
+
+               case ')': /* end+loop if */
+                       /* The stack must always be popped. */
+                       if (SP <= 0)
+                               goto badnest;
+                       --SP;
+                       /* If fnoexec>2 then this is a nested disabled block or, in
+                        * the case of 3, a break.  In neither case is the expression
+                        * evaluated and the test setting is for an enclosing block.
+                        */
+                       if (fnoexec > 2) {
+                               fnoexec -= 3;
+                       } else {
+                               /* In this case there must be a test. */
+                               if (test == 0)
+                                       goto badnest;
+
+                               /* Execution resumes regardless. */
+                               fnoexec = 0;
+                               assert(!pp->fbreak);
+
+                               /* So make the loop test now - this may cause a branch back
+                                * to the ( and that will push the stack again.  Evaluate
+                                * the test.
+                                */
+                               (void)parse_expression(pp, line, test, Ac);
+                               if (pp->fbreak)
+                                       pp->fbreak = 0;
+                               else if (top(pp, lp) != 0) {
+                                       Ac = stack[SP]-1; /* Ac is incremented below */
+                               }
+
+                               /* And the test has been consumed. */
+                               test = 0;
+                       }
+                       break;
+
+               badnest:
+                       die("bad [: or [) nesting", lp);
+                       break;
+
+               case ';':
+               case '\n':
+                       /* end of line terminates the loop, but Ac is stepped beyond the
+                        * terminator.
+                        */
+                       ++Ac;
+                       goto end;
+
+               case ' ':
+               case '\f':
+               case '\r':
+               case '\t':
+               case '\v':
+               case ',':  /* Treat as a space */
+                       /* Skip other white space. */
+                       break;
+
+                       /* Everything else is glommed together because the fnoexec case
+                        * can be simply handled by skipping character-by-character (because
+                        * (:?); do not occur inside numbers!)
+                        */
+               default:
+                       if (fnoexec)
+                               break;
+
+                       if (isupper(ch))
+                               push(pp, pp->variables[ch], lp);
+                       else if (isdigit(ch)) {
+                               char *end = (char*)lp;
+                               unsigned long num;
+                               errno = 0;
+                               num = strtoul(lp, &end, 0);
+                               if (num == ULONG_MAX && (errno == EINVAL || errno == ERANGE))
+                                       pdie("invalid number", lp);
+                               push(pp, num, lp);
+                               /* strotul returns a pointer to the first invalid character in
+                                * end, so Ac becomes end-line-1, because it is incremented below.
+                                */
+                               Ac = (const unsigned char*)end-line-1;
+                       } else {
+                               /* The operators are handled here.  An unrecognised character is an
+                                * error at this point.  The left, right are always valid because
+                                * the stack has 8 unused slots at the top...
+                                */
+                               unsigned long left = pp->stack[pp->cstack - 2];
+                               unsigned long right = pp->stack[pp->cstack - 1];
+
+                               switch (ch) {
+                               case '?': /* break */
+                                       /* break inside a condition is actually allowed, so this may
+                                        * happen with SP==0 while evaluating a condition.  For the
+                                        * moment ? is also allowed outside a loop, it terminates the
+                                        * processing of the whole expression.
+                                        */
+                                       if (top(pp, lp) != 0) {
+                                               /* break: skip to the ) and do not do the test on
+                                                * that either.
+                                                */
+                                               fnoexec = 3;
+                                       }
+                                       break;
+
+                               #define DIOP(operator) op(pp, 2, left operator right, lp); break
+                               #define MONOP(operator) op(pp, 1, operator right, lp); break
+                                       /* The C operators */
+                               case '*': DIOP(*);
+                               case '+': DIOP(+);
+                               case '-': DIOP(-);
+                               case '/': DIOP(/);
+                               case '%': DIOP(%);
+                               case '<': DIOP(<);
+                               case '>': DIOP(>);
+                               case '|': DIOP(|);
+                               case '&': DIOP(&);
+                               case '^': DIOP(^);
+                               case '~': MONOP(~);
+                               case '!': MONOP(!);
+                               case '=': /* equality */
+                                       op(pp, 2, left == right, lp);
+                                       break;
+                               case '{': /* shift left */
+                                       op(pp, 2, left << right, lp);
+                                       break;
+                               case '}': /* shift right */
+                                       op(pp, 2, left >> right, lp);
+                                       break;
+                               case 'r': /* rotate right */
+                                       op(pp, 2, (left >> right) + (left << (32-right)), lp);
+                                       break;
+                               case 'e': /* sign extend (right is number of valid bits). */
+                                       op(pp, 2, (long)(left << (32-right)) >> (32-right), lp);
+                                       break;
+                               case 'm': /* mask (right is number of valid bits). */
+                                       op(pp, 2, (left << (32-right)) >> (32-right), lp);
+                                       break;
+                               case '$': /* Size of input. */
+                                       if (pp->pfrom == 0)
+                                               die("size of input unknown", lp);
+                                       push(pp, size_mtd(pp->pfrom), lp);
+                                       break;
+                               case 'f': /* position of input ('from' pointer). */
+                                       if (pp->pfrom == 0)
+                                               die("position of input unknown", lp);
+                                       push(pp, pp->pfrom->useroffset, lp);
+                                       break;
+                               case '#': /* Size of output. */
+                                       if (pp->pto == 0)
+                                               die("size of output unknown", lp);
+                                       push(pp, size_mtd(pp->pto), lp);
+                                       break;
+                               case 't': /* position of output ('to' pointer). */
+                                       if (pp->pto == 0)
+                                               die("position of output unknown", lp);
+                                       push(pp, pp->pto->useroffset, lp);
+                                       break;
+                               case 'd': /* device number of the input device */
+                                       if (pp->pfrom == 0)
+                                               die("input device number unknown", lp);
+                                       push(pp, pp->pfrom->stat.st_rdev == 0 ? pp->pfrom->stat.st_dev :
+                                                       pp->pfrom->stat.st_rdev, lp);
+                                       break;
+                               case '@': /* one byte read. */
+                                       push(pp, inb(pp), lp);
+                                       break;
+                               case 'b': /* big endian 4 byte read. */
+                                       #define P(st) (void)parse_expression(pp, st, 0, (sizeof st)-1)
+                                       P("@8{@+8{@+8{@+;# 4 byte big-endian read");
+                                       break;
+                               case 'l': /* little endian 4 byte read. */
+                                       P("@@@@8{+8{+8{+;# 4 byte little-endian read");
+                                       break;
+                               case '.': /* copy (dup) */
+                                       push(pp, right, lp);
+                                       break;
+                               case 'p': /* pop */
+                                       pop(pp, 1, lp);
+                                       break;
+                               case 's': /* swap (top two elements of the stack) */
+                                       pop(pp, 2, lp);
+                                       push(pp, right, lp);
+                                       push(pp, left, lp);
+                                       break;
+                               default:
+                                       die("invalid operator", lp);
+                               }
+                       }
+               }
+       }
+
+       /* Here on terminator or Ac==AcEnd. */
+end:
+       /* If SP>0 then there was some bad nesting going on - i.e. the brackets have
+        * not been closed.  fnoexec must be zero if SP is 0.
+        */
+       if (SP > 0)
+               die("unclosed ( )", line);
+       assert(fnoexec == 0 || fnoexec == 3);
+       if (fnoexec == 3)
+               pp->fbreak = 1;
+       return Ac;
+}
+
+
+/* Parse an expression and return numbers off the top of stack. */
+static int need(parse_buf *pp, unsigned long arg[2],
+               const unsigned char *line, int Ac, int AcEnd, int num) {
+       int Acnew = parse_expression(pp, line, Ac, AcEnd);
+
+       assert(num <= 2);
+       if (pp->cstack < STACK_BASE+num)
+               die("too few arguments", line+Ac);
+
+       while (num > 0)
+               arg[--num] = pp->stack[--(pp->cstack)];
+
+       /* cancel a break at the top level. */
+       pp->fbreak = 0;
+       return Acnew;
+}
+
+
+/* Parse a string.  The string ends up in the buffer and is limited in
+ * size to STR_MAX.  The API returns a null string for empty quoted
+ * strings and for the unquoted case where there is only whitespace
+ * in the rest of the command.  The result is the index of the first
+ * character after the end of the command (*not* the end of the string.)
+ */
+static int string(unsigned char *buffer, const unsigned char *line, int Ac) {
+       int start, end;
+
+       while (isspace(line[Ac]) && line[Ac] != '\n') ++Ac;
+
+       if (line[Ac] == '"' || line[Ac] == '\'') {
+               const unsigned char quote = line[Ac];
+               start = Ac;
+               end = Ac+1;
+               while (line[end] != 0 && line[end] != '\n' && line[end] != quote) ++end;
+               if (line[end] != quote)
+                       die("unterminated quoted string", line+start);
+               Ac = end+1;
+               while (isspace(line[Ac]) && line[Ac] != '\n') ++Ac;
+               if (line[Ac] != 0 && line[Ac] != ';' && line[Ac] != '\n')
+                       die("stuff on line after quoted string", line+start);
+               ++start;
+       } else {
+               start = Ac;
+               while (line[Ac] != 0 && line[Ac] != ';' && line[Ac] != '\n') ++Ac;
+               end = Ac;
+       }
+
+       end -= start;
+       if (end >= STR_MAX)
+               die("string too long", line+start);
+
+       /* Copy out the string into the buffer and null terminate it. */
+       memcpy(buffer, line+start, end);
+       buffer[end] = 0;
+
+       /* Skip over EOL, if present, and store the string pointer back. */
+       if (line[Ac] != 0) ++Ac;
+       return Ac;
+}
+
+
+/* Find the end of line, Ac always points to the start. */
+static int eol(const unsigned char *line, int Ac) {
+       unsigned char quote = 0;
+       while (line[Ac] != '\n' && (line[Ac] != ';' || quote)) {
+               if (line[Ac] == 0)
+                       return Ac;
+               if (line[Ac] == '"' || line[Ac] == '\"') {
+                       if (quote == 0)
+                               quote = line[Ac];
+                       else if (quote == line[Ac])
+                               quote = 0;
+               }
+               ++Ac;
+       }
+       return Ac+1;
+}
+
+
+/* Find a file given its name. */
+static mtd_file *find_file(parse_buf *pp, const unsigned char *name) {
+       int i;
+       for (i=0; i<NUM_FILES; ++i) {
+               if (pp->files[i].pname != 0 &&
+                       strcmp(name, pp->files[i].pname) == 0)
+                       return pp->files+i;
+       }
+       return 0;
+}
+
+
+/* Open a file for read or write. */
+static mtd_file *open_file(parse_buf *pp, const unsigned char *name, int fwrite) {
+       int i;
+       for (i=0; i<NUM_FILES; ++i)
+               if (pp->files[i].pname == 0)
+                       break;
+       if (i >= NUM_FILES)
+               die("no more files", name);
+       open_mtd(pp->files+i, name, fwrite, pp->fverify);
+       return pp->files+i;
+}
+
+
+/* Basic parse function.  Does all the work.  Simple line-by-line command
+ * parser.  The input is a vector of strings (an argv), 'input' says where
+ * it came from.
+ */
+static void parse(parse_buf *pp, int lines, const char **prog) {
+       /* The program pointer is a line index Al and a character within the
+        * line Ac.  The exec stack just has those references (i.e. (Al,Ac)
+        * is a PC).
+        */
+       int Al = 0, SP = 0, fnoexec = 0;
+       struct stack {
+               int Al, Ac;
+       } stack[16];
+
+       /* fnoexec has these values:
+        *    0: normal execution
+        *    1: within an if $($:$) and none of the tests have passed (so
+        *       nothing has been executed in this block.)
+        *    2: within an if and a test has passed (a previous block has
+        *       been executed.)
+        *    3: within an if and a break ($?) has succeeded, execute nothing
+        *       until *and including* the $) (i.e. do not evaluate the value
+        *       on that either.)
+        * This applies to nested if blocks too when the block is executable.
+        * If a block is nested within a non-executing environment the $(
+        * adds 3 to fnoexec.  The $) always subtracts 3 unless fnoexec is
+        * 0, 1, 2 or 3 thus the prior state is reliably restored.
+        */
+
+       while (Al < lines) {
+               int Ac = 0;
+               const unsigned char *line = prog[Al];
+               const int AcEnd = strlen(line);
+
+               while (line[Ac]) {
+                       const unsigned char *lp = line+Ac;
+                       unsigned char ch = *lp;
+
+                       /* A command is two characters, except that ';' is end-of-line,
+                        * skip these things until we see a command, then use the next
+                        * two characters.  line[Ac] != 0 so the read of Ac+1 is safe.
+                        */
+                       if (isspace(ch) || ch == ';') {
+                               ++Ac;
+                       } else if ((ch != '$' && fnoexec) || ch == '#') {
+                               /* disabled execution and not a control flow command, or
+                                * a comment.
+                                */
+                               Ac = eol(line, Ac);
+                       } else switch ((ch << 8) + line[Ac+1]) {
+                               unsigned long arg[2];
+                               unsigned char buffer[STR_MAX];
+
+                               /* Control flow.  These commands have to explicitly check
+                                * the fnoexec state because they manipulate it.
+                                */
+                       #define CASE(l,r) case (((l)<<8)+(r))
+                       CASE('$','('): /* if */
+                               if (SP >= 16)
+                                       die("exec stack overflow", lp);
+                               stack[SP].Al = Al;
+                               stack[SP++].Ac = Ac;
+                               if (fnoexec) {
+                                       fnoexec += 3;
+                                       goto noexec;
+                               }
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               fnoexec = arg[0] == 0;
+                               break;
+
+                       CASE('$',':'): /* elif */
+                               /* fnoexec is 1 if nothing has executed in this block yet,
+                                * and if the block itself is executing.
+                                */
+                               if (SP <= 0)
+                                       goto underflow;
+                               if (fnoexec != 1) {
+                                       /* If the previous block executed record this. */
+                                       if (fnoexec == 0)
+                                               fnoexec = 2;
+                                       goto noexec;
+                               }
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               fnoexec = arg[0] == 0;
+                               break;
+
+                       CASE('$',')'): /* end+loop if */
+                               /* The stack must always be popped. */
+                               if (SP <= 0)
+                                       goto underflow;
+                               --SP;
+                               /* If fnoexec>2 then this is a nested disabled block or, in
+                                * the case of 3, a break.  In neither case is the expression
+                                * evaluated.
+                                */
+                               if (fnoexec > 2) {
+                                       fnoexec -= 3;
+                                       goto noexec;
+                               }
+                               fnoexec = 0;
+                               /* So make the loop test now - this may cause a branch back
+                                * to the $( and that will push the stack again.
+                                */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               if (arg[0] != 0) {
+                                       Al = stack[SP].Al;
+                                       Ac = stack[SP].Ac;
+                                       /* Do not ever forget this, because we may not exit the
+                                        * immediately enclosing loop and line is set up outside
+                                        * it!
+                                        */
+                                       line = prog[Al];
+                               }
+                               break;
+
+                       CASE('$','?'): /* break */
+                               if (SP <= 0)
+                                       goto underflow;
+                               if (fnoexec)
+                                       goto noexec;
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               if (arg[0] != 0) {
+                                       /* break: skip to the $) and do not do the test on
+                                        * that either.
+                                        */
+                                       fnoexec = 3;
+                               }
+                               break;
+
+                       underflow:
+                               die("exec stack underflow", lp);
+                       noexec:
+                               /* Control flow no-exec case. */
+                               Ac = eol(line, Ac);
+                               break;
+
+                               /* Program termination */
+                       CASE('!','?'): /* exit if non-zero */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               if (arg[0] != 0)
+                                       quit(pp, arg[0]);
+                               break;
+
+                       CASE('!','!'): /* exit unconditionally */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               quit(pp, arg[0]);
+                               break;
+
+                               /* Input/output */
+                       CASE('>','>'): /* write to (no argument reverts to stdout) */
+                               Ac = string(buffer, line, Ac+2);
+                               if (buffer[0] != 0) {
+                                       mtd_file *pfile = find_file(pp, buffer);
+                                       if (pfile == 0)
+                                               pfile = open_file(pp, buffer, 1);
+                                       else if (!pfile->fwrite)
+                                               die("attempt to open a read file for write", buffer);
+                                       pp->pto = pfile;
+                               } else
+                                       pp->pto = 0;
+                               break;
+
+                       CASE('<','<'): /* read from */
+                               Ac = string(buffer, line, Ac+2);
+                               if (buffer[0] != 0) {
+                                       mtd_file *pfile = find_file(pp, buffer);
+                                       if (pfile == 0)
+                                               pfile = open_file(pp, buffer, 0);
+                                       pp->pfrom = pfile;
+                               } else
+                                       pp->pfrom = 0;
+                               break;
+
+                       CASE('<','>'): /* close */
+                       CASE('>','<'): /* synonym of close */
+                               Ac = string(buffer, line, Ac+2);
+                               if (buffer[0] != 0) {
+                                       mtd_file *pfile = find_file(pp, buffer);
+                                       if (pfile == 0)
+                                               die("no such file to close", buffer);
+
+                                       if (pfile == pp->pfrom)
+                                               pp->pfrom = 0;
+                                       if (pfile == pp->pto)
+                                               pp->pto = 0;
+                                       close_mtd(pfile);
+                               } else
+                                       die("<> (close) requires an argument", lp);
+                               break;
+
+                       CASE('>','='): /* set to pointer */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               if (pp->pto == 0)
+                                       die("no output file", lp);
+                               pp->pto->useroffset = arg[0];
+                               break;
+
+                       CASE('<','='): /* set from pointer */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               if (pp->pfrom == 0)
+                                       die("no input file", lp);
+                               pp->pfrom->useroffset = arg[0];
+                               break;
+
+                               /* writing to output */
+                       CASE('w','b'): /* bigendian number bytes */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 2);
+                               switch (arg[1]) {
+                               case 4: outb(pp, arg[0] >> 24);
+                               case 3: outb(pp, arg[0] >> 16);
+                               case 2: outb(pp, arg[0] >>  8);
+                               case 1: outb(pp, arg[0]);
+                                               break;
+                               default:die("byte count out of range", lp);
+                               }
+                               break;
+
+                       CASE('w','l'): /* little endian number bytes */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 2);
+                               switch (arg[1]) {
+                               case 4: outb(pp, arg[0]); arg[0] >>= 8;
+                               case 3: outb(pp, arg[0]); arg[0] >>= 8;
+                               case 2: outb(pp, arg[0]); arg[0] >>= 8;
+                               case 1: outb(pp, arg[0]);
+                                               break;
+                               default:die("byte count out of range", lp);
+                               }
+                               break;
+
+                       CASE('w','s'): /* write string */
+                               Ac = string(buffer, line, Ac+2);
+                               outputbytes(pp, buffer, strlen(buffer));
+                               break;
+
+                       CASE('f','b'): /* fill bytes */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 2);
+
+                               /* The likely error is forgetting an argument, that may
+                                * put the byte count into the fill value.
+                                */
+                               if (arg[1] > 255)
+                                       die("fill value out of range", lp);
+
+                               /* Now call fillbytes, arguments same order as memset, but
+                                * the reverse from the command (fb number value).
+                                */
+                               fillbytes(pp, arg[1], arg[0]);
+                               break;
+
+                       CASE('c','p'): /* copy bytes */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               copybytes(pp, arg[0]);
+                               break;
+
+                               /* writing to stdout */
+                       CASE('p','r'): /* printf("%lu\n") */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                               printf("%lu\n", arg[0]);
+                       stdout_check:
+                               if (ferror(stdout))
+                                       pdie("write error", "stdout");
+                               break;
+
+                       CASE('p','f'): /* printf(string) */
+                               Ac = string(buffer, line, Ac+2);
+                               /* This flushes the whole stack, so "printf;" can be useful
+                                * under some circumstances!
+                                */
+                                       {
+                                       unsigned long *stack = pp->stack + pp->cstack - 1;
+                                       pp->cstack = STACK_BASE;
+                                       printf(buffer, stack[0], stack[-1], stack[-2], stack[-3],
+                                                                       stack[-4], stack[-5], stack[-6], stack[-7]);
+                                       }
+                               goto stdout_check;
+
+                       CASE('p','n'): /* printf("\n") */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 0);
+                               printf("\n");
+                               goto stdout_check;
+
+
+                       CASE('.','='): /* eval */
+                               Ac = need(pp, arg, line, Ac+2, AcEnd, 0);
+                               break;
+
+                       default:
+                               /* Assignment is handled here. */
+                               if (isupper(ch) && line[Ac+1] == '=') {
+                                       /* Assignment. */
+                                       Ac = need(pp, arg, line, Ac+2, AcEnd, 1);
+                                       pp->variables[ch] = arg[0];
+                               } else
+                                       die("unknown command", lp);
+                               break;
+                       }
+               }
+
+               /* end of line (line[Ac] is '\0'), move to the next line. */
+               ++Al;
+       }
+
+       /* If SP>0 then there was some bad nesting going on - i.e. the brackets have
+        * not been closed.  fnoexec must be zero if SP is 0.
+        */
+       if (SP > 0)
+               die("unclosed $( $)", "eof");
+       assert(fnoexec == 0);
+}
+
+/* The command only understands two options, -v for verify and -h for
+ * help.
+ */
+int main(int argc, const char **argv) {
+       int i = 0;
+       parse_buf p;
+
+       /* the option must be the first argument. */
+       if (argc < 2 || strcmp(argv[1], "-h") == 0) {
+               fprintf(stderr, "%s: usage: %s ([-v] {comands} |-h [command])\n", argv[0], argv[0]);
+#if HELP
+               if (argc < 3) {
+                       fprintf(stderr, " options:\n"
+                                                       "  -v: do not write, just verify a previous write\n"
+                                                       "  -h: output this help\n"
+                                                       "  -h commands: output command help\n"
+                                                       "  -h <command>: output help for command <command>\n");
+               } else {
+                       for (i=2; i<argc; ++i) {
+                               const unsigned char *p = argv[i];
+                               switch ((argv[i][0] << 8) + argv[i][1]) {
+                               CASE('a','d'):
+                                       p = "Enter the keyword in [] contained after the command list\n"
+                                               "for more information about those commands";
+                                       break;
+                               CASE('c','o'):
+                                       if (argv[i][2] != 'n')
+                                               p = " commands are terminated by ';', newline or the end of input:\n"
+                                               "  <command> {arguments} ;\n\n"
+                                               " command: a two character command:       [additional info]\n"
+                                               "  conditional execution: $( $: $? $)     [conditional]\n"
+                                               "  program termination:   !? !!           [exit]\n"
+                                               "  input/output control:  >> << <> >= <=  [io]\n"
+                                               "  writing to the output: wb wl ws fb cp  [write]\n"
+                                               "  writing to stdout:     pr pf pn        [messages]\n"
+                                               "  assignment:            <C>= .=         [assign]\n"
+                                               "  comment:               #\n\n"
+                                               "  -h <command>: output the syntax of command <command>\n\n"
+                                               " arguments: a string or a postfix numeric expression\n"
+                                               "  -h string:   output the syntax of a string argument\n"
+                                               "  -h number:   output the syntax of a numeric argument\n"
+                                               "  -h operator: output a summary of the operators\n\n"
+                                               " A command which starts with '#' is ignored (a comment).";
+                                       else
+                                               p = "conditional evalation\n"
+                                               "  Conditional evalation has the general form:\n\n"
+                                               "    $( cond; $? cond; $: cond; $) cond;  (command form)\n"
+                                               "    cond(,  cond?,  [cond:,  [cond),     (operator from)\n\n"
+                                               "  Conditional commands bracket other commands, conditional\n"
+                                               "  operators bracket other numbers and operators in the expression\n"
+                                               "  The conditions (cond) are intrepreted as boolean values, with 0\n"
+                                               "  false and any other value true.  Notice that the numbers come off\n"
+                                               "  the number stack so it is not necessary for them to be given with\n"
+                                               "  the specific command or before the specifc operator but it can\n"
+                                               "  be very confusing if this is not done.\n\n"
+                                               "  The commands/operators behave exactly as the following ANSI C\n"
+                                               "  syntax:\n\n"
+                                               "    $( n       do if (n) {\n"
+                                               "    $? n       if (n) break;\n"
+                                               "    $: n       } else if (n) {\n"
+                                               "    $) n       } while (n);\n\n"
+                                               "  Thus a simple if/then/else sequence might be:\n\n"
+                                               "    $( condition; $: 1; $) 0\n"
+                                               "    condition( [1: [0)\n\n"
+                                               "  And a simple do while loop:\n\n"
+                                               "    $( 1; $) condition\n"
+                                               "    1( [condition)\n\n"
+                                               "  Notice that the operator syntax requires [cond: and [cond)";
+                                       break;
+                               CASE('$','('): /* if */
+                                       p = "conditional: $( condition;  do if (condition) {\n";
+                                       break;
+                               CASE('$',':'): /* elif */
+                                       p = "conditional: $( condition;  } else if (condition) {\n";
+                                       break;
+                               CASE('$',')'): /* end+loop if */
+                                       p = "conditional: $: condition;  } while (condition);\n";
+                                       break;
+                               CASE('$','?'): /* break */
+                                       p = "conditional: $: condition;  if (condition) break;\n";
+                                       break;
+                               CASE('e','x'):
+                                       p = "program termination commands:\n"
+                                               "  !? and !! cause immediate termination of the program.\n"
+                                               "  The currently opened files are closed (and flushed) on\n"
+                                               "  termination and the program exits with the given code.\n"
+                                               "  !? is condition - it only causes an exit if the code is\n"
+                                               "  non zero.";
+                                       break;
+                               CASE('!','?'): /* exit if non-zero */
+                                       p = "exit: !? code;  if (code != 0) exit(code);\n";
+                                       break;
+                               CASE('!','!'): /* exit unconditionally */
+                                       p = "exit: !! code;  exit(code);\n";
+                                       break;
+                               CASE('i','o'):
+                                       p = "input/output control:\n"
+                                               "  >> << allow files to be opened for write or read (respectively)\n"
+                                               "  A file may be opened for both write and read, but in that case\n"
+                                               "  the first open must be the write one.  Such a file has only one\n"
+                                               "  position pointer.  Supplying an empty file name to the commands\n"
+                                               "  causes the standard IO stream (stdout or stdin) to be used, but\n"
+                                               "  the file remains open.  A file may be reselected by repeating\n"
+                                               "  the open command.  The position pointer will not have changed.\n\n"
+                                               "  <> closes a file.  If it is the current input or output then the\n"
+                                               "  input or output, as appropriate, is redirected to the standard\n"
+                                               "  streams.\n\n"
+                                               "  <= >= set the input (from) or output (to) pointer.  The pointer is\n"
+                                               "  changed on the current input or output device and is a property of\n"
+                                               "  that device, not a global setting.";
+                                       break;
+                               CASE('>','>'): /* write to (no argument reverts to stdout) */
+                                       p = "io: >> file;  select file as the current output device\n"
+                                               "    >> ;      select stdout as the current output device\n";
+                                       break;
+                               CASE('<','<'): /* read from */
+                                       p = "io: << file;  select file as the current input device\n"
+                                               "    << ;      select stdin as the current input device\n";
+                                       break;
+                               CASE('<','>'): /* close */
+                                       p = "io: <> file;  close file\n";
+                                       break;
+                               CASE('>','<'): /* synonym of close */
+                                       p = "io: <> file;  close file\n";
+                                       break;
+                               CASE('>','='): /* set to pointer */
+                                       p = "io: >= offset;  set the 'to' pointer: output device file pointer\n";
+                                       break;
+                               CASE('<','='): /* set from pointer */
+                                       p = "io: <= offset;  set the 'from' pointer: input device file pointer\n";
+                                       break;
+                               CASE('w','r'):
+                                       p = "writing to the output:\n"
+                                               "  wb, wl, ws, fb and cp write bytes to the current output device at\n"
+                                               "  the current output position (which may be set using >=.)\n\n"
+                                               "  See the individual command descriptions for more information.";
+                                       break;
+                               CASE('w','b'): /* bigendian number bytes */
+                                       p = "write: wb number bytes; write number in bytes big endian bytes\n";
+                                       break;
+                               CASE('w','l'): /* little endian number bytes */
+                                       p = "write: wl number bytes; write number in bytes little endian bytes\n";
+                                       break;
+                               CASE('w','s'): /* write string */
+                                       p = "write: wl string;       write string\n";
+                                       break;
+                               CASE('f','b'): /* fill bytes */
+                                       p = "write: fb number byte;  write number bytes of value 'byte'\n";
+                                       break;
+                               CASE('c','p'): /* copy bytes */
+                                       p = "write: cp number;       copy number bytes from input to output\n";
+                                       break;
+                               CASE('m','e'):
+                                       p = "writing to stdout:\n"
+                                               "  pr pf and pn allow output to stdout ignoring the current output\n"
+                                               "  device.  pf uses a C printf(3) format string and no checking is\n"
+                                               "  made on the validity or number of parameters.  The program will\n"
+                                               "  crash if a pointer format (like %s) is used.  This is by design.\n"
+                                               "  Use of \\ escapes within the string will probably not produce the\n"
+                                               "  expected result.  The only way to output a newline is via the\n"
+                                               "  separate pn command.";
+                                       break;
+                               CASE('p','r'): /* printf("%lu\n") */
+                                       p = "message: pr number;   printf(\"%lu\\n\", number);\n";
+                                       break;
+                               CASE('p','f'): /* printf(string) */
+                                       p = "message: pf string;   printf(string, 1, 2, 3, 4, 5, 6, 7, 8)\n"
+                                               "                      uses string as a format string for the\n"
+                                               "                      top (up to) 8 arguments on the stack\n"
+                                               "                      Empties the stack!\n";
+                                       break;
+                               CASE('p','n'): /* printf("\n") */
+                                       p = "message: pn;          prints a newline (printf(\"\\n\");)\n";
+                                       break;
+                               CASE('.','='):
+                                       p = ".= [number]\n"
+                                               "  The number is evaluated and pushed to the stack";
+                               CASE('a','s'):
+                                       p = "<C>= [number] (<C> is an upper case character in the range A-Z)\n"
+                                               "  The number is assigned to the variable <C>.";
+                                       break;
+                               CASE('s','t'):
+                                       p = "string format\n"
+                                               "  A string is either quoted or unquoted.  An unquoted string is\n"
+                                               "  the rest of the line, terminated by ';' or newline (or the end\n"
+                                               "  of the command).  A quoted string is enclosed in either single\n"
+                                               "  or double quotes (which are removed) and must be the only thing\n"
+                                               "  on the line apart from white space.";
+                                       break;
+                               CASE('n','u'):
+                                       p = "number format\n"
+                                               "  A number is a post-fix expression consisting of numeric values,\n"
+                                               "  variable values and operators.\n"
+                                               "  A numeric value is anything starting with a digit.  0x prefixes a\n"
+                                               "  hexadecimal value, 0 prefixes an octal value otherwise the value\n"
+                                               "  is interpreted as decimal.\n"
+                                               "  Variables are single upper case characters in the range A-Z,\n"
+                                               "  thus 26 variables are available.\n"
+                                               "  Numeric values and variables are pushed onto a stack which can\n"
+                                               "  store up to 248 numbers.\n"
+                                               "  Operators are single characters not identified as numeric values\n"
+                                               "  or variables.  They operate on the stack combining elements.\n"
+                                               "  Use -h operators for more information.";
+                                       break;
+                               CASE('o','p'):
+                                       p = "operators\n"
+                                               "  The following operators are defined.  Operators take one or\n"
+                                               "  two elements from the stack and push back up to two elements\n"
+                                               "  The conditional operators (:?) are special, see the description\n"
+                                               "  under -h conditionals for more information.  (Conditional\n"
+                                               "  operators and the conditional commands behave identically.)\n\n"
+                                               "  In the following descriptions 'left' is the last-but-one\n"
+                                               "  number on the stack and 'right' is the last number pushed.\n"
+                                               "  'new' is a new number pushed on to the top of the stack.\n\n"
+                                               " arithmetic operators:\n"
+                                               "     *: new := left * right        (multiplication)\n"
+                                               "     +: new := left + right\n"
+                                               "     -: new := left - right\n"
+                                               "     /: new := left / right        (division)\n"
+                                               "     %: new := left % right        (i.e. C style modulus)\n"
+                                               "     <: new := left < right\n"
+                                               "     >: new := left > right\n"
+                                               "     =: new := left == right       (i.e. equality)\n"
+                                               "     |: new := left | right        (bitwise or)\n"
+                                               "     &: new := left | right        (bitwise and)\n"
+                                               "     ^: new := left | right        (bitwise xor)\n"
+                                               "     ~: new := ~right              (ones complement)\n"
+                                               "     !: new := !right              (logical complement - right == 0)\n"
+                                               "     {: new := left << right       (bitwise shift left by right bits)\n"
+                                               "     }: new := left << right       (bitwise shift right by right bits)\n"
+                                               "     r: new := left ROR right      (bitwise rotate right by right bits)\n"
+                                               "     e: new := left SXT right      (right least significant bits of\n"
+                                               "                                    left are sign extended to 32 bits)\n"
+                                               "     m: new := left MASK right     (right least significant bits of left\n"
+                                               "                                    are masked, upper bits become 0)\n"
+                                               " operators to read bytes:\n"
+                                               "     @: new := input-byte          (one byte is read from current input)\n"
+                                               "     b: new := input-big-endian    (4 big endian bytes are read)\n"
+                                               "     l: new := input-little-endian (4 little endian bytes)\n"
+                                               " enquiry operators:\n"
+                                               "     $: the size of the current input device\n"
+                                               "     f: the current 'from' offset\n"
+                                               "     #: the size of the current output device\n"
+                                               "     t: the current 'to' offset\n"
+                                               "     d: the full device number of the current input device\n"
+                                               "        this returns the number of the underlying device for a file\n"
+                                               " operators to manipulate the stack:\n"
+                                               "     .: new := right,right         (i.e. right is duplicated)\n"
+                                               "     p: pop - right is removed from the stack\n"
+                                               "     s: swap - right and left are swapped on the stack\n"
+                                               " conditional operators:\n"
+                                               "     ( exec := right         (execution is stopped if right is 0)\n"
+                                               "     : exec := else if right (execution is resumed or stopped)\n"
+                                               "     ? if right break        (break out of if/loop if right)\n"
+                                               "     ) loop if right         (end of current if/loop)";
+                                       break;
+                               default:
+                                       fprintf(stderr, "command '%s' not recognised\n", p);
+                                       p = 0;
+                               }
+
+                               if (p != 0)
+                                       fprintf(stderr, "%s\n", p);
+                       }
+               }
+#endif
+               exit(1);
+       } else if (strcmp(argv[1], "-v") == 0)
+               i = 1;
+               
+       init_parse(&p, i);
+       ++i;
+       parse(&p, argc-i, argv+i);
+       quit(&p, 0);
+}
index 4ed4e10..8d2ecdc 100644 (file)
@@ -39,8 +39,11 @@ echodns(){
 #
 # It is important not to hard-wire the name of the device because of
 # the posibility of changing the flash partition layout.
-sysdev="$(mtdev SysConf)"
-if test -n "$sysdev" -a -c "$sysdev"
+#
+# The block device is used here because at present udev does not
+# show the character devices
+sysdev="$(mtblockdev SysConf)"
+if test -n "$sysdev" -a -b "$sysdev"
 then
        # Read the defined part of SysConf into /etc/default/sysconf.
        # SysConf has lines of two forms:
index 00cac5c..be4f027 100644 (file)
@@ -2,7 +2,7 @@ DESCRIPTION = "Openslug initial network config via sysconf"
 SECTION = "console/network"
 LICENSE = "GPL"
 DEPENDS = "base-files"
-PR = "r25"
+PR = "r26"
 
 SRC_URI = "file://linuxrc \
           file://boot/flash \
@@ -15,7 +15,6 @@ SRC_URI = "file://linuxrc \
           file://rmrecovery \
           file://sysconfsetup \
           file://turnup \
-          file://modutils.txt \
           file://modprobe.conf \
           file://leds_rs_green \
           file://leds_startup \
@@ -92,7 +91,6 @@ do_install() {
        done
 
        # Configuration files
-       #XXinstall -m 0644 modutils.txt ${D}${sysconfdir}/modutils/
        install -m 0644 modprobe.conf ${D}${sysconfdir}/
 
        set +ex
@@ -122,5 +120,6 @@ pkg_postrm_openslug-init() {
 PACKAGES = "${PN}"
 FILES_${PN} = "/"
 
-#CONFFILES_${PN} = "${sysconfdir}/modutils/modutils.txt ${sysconfdir}/modprobe.conf"
-CONFFILES_${PN} = "${sysconfdir}/modprobe.conf"
+# It is bad to overwrite /linuxrc as it puts the system back to
+# a flash boot (and the flash has potentially not been upgraded!)
+CONFFILES_${PN} = "${sysconfdir}/modprobe.conf /linuxrc"